/*
 * DHD Bus Module for SDIO
 *
 * Copyright (C) 1999-2019, Broadcom.
 *
 *      Unless you and Broadcom execute a separate written software license
 * agreement governing use of this software, this software is licensed to you
 * under the terms of the GNU General Public License version 2 (the "GPL"),
 * available at http://www.broadcom.com/licenses/GPLv2.php, with the
 * following added to such license:
 *
 *      As a special exception, the copyright holders of this software give you
 * permission to link this software with independent modules, and to copy and
 * distribute the resulting executable under terms of your choice, provided that
 * you also meet, for each linked independent module, the terms and conditions
 * of the license of that module.  An independent module is a module which is
 * not derived from this software.  The special exception does not apply to any
 * modifications of the software.
 *
 *      Notwithstanding the above, under no circumstances may you combine this
 * software in any way with any other Broadcom software provided under a license
 * other than the GPL, without Broadcom's express prior written consent.
 *
 *
 * <<Broadcom-WL-IPTag/Open:>>
 *
 * $Id: dhd_sdio.c 825481 2019-06-14 10:06:03Z $
 */

#include <typedefs.h>
#include <osl.h>
#include <bcmsdh.h>

#include <bcmdefs.h>
#include <bcmutils.h>
#include <bcmendian.h>
#include <bcmdevs.h>

#include <siutils.h>
#include <hndpmu.h>
#include <hndsoc.h>
#include <bcmsdpcm.h>
#include <hnd_armtrap.h>
#include <hnd_cons.h>
#include <sbchipc.h>
#include <sbhnddma.h>

#include <sdio.h>
#ifdef BCMSPI
#include <spid.h>
#endif /* BCMSPI */
#include <sbsdio.h>
#include <sbsdpcmdev.h>
#include <bcmsdpcm.h>
#include <bcmsdbus.h>

#include <ethernet.h>
#include <802.1d.h>
#include <802.11.h>

#include <dngl_stats.h>
#include <dhd.h>
#include <dhd_bus.h>
#include <dhd_proto.h>
#include <dhd_dbg.h>
#include <dhdioctl.h>
#include <sdiovar.h>
#include <dhd_config.h>
#ifdef DHD_PKTDUMP_TOFW
#include <dhd_linux_pktdump.h>
#endif
#include <linux/mmc/sdio_func.h>
#include <dhd_linux.h>

#ifdef PROP_TXSTATUS
#include <dhd_wlfc.h>
#endif // endif
#ifdef DHDTCPACK_SUPPRESS
#include <dhd_ip.h>
#endif /* DHDTCPACK_SUPPRESS */

#ifdef BT_OVER_SDIO
#include <dhd_bt_interface.h>
#endif /* BT_OVER_SDIO */

#if defined(DEBUGGER) || defined(DHD_DSCOPE)
#include <debugger.h>
#endif /* DEBUGGER || DHD_DSCOPE */

bool dhd_mp_halting(dhd_pub_t *dhdp);
extern void bcmsdh_waitfor_iodrain(void *sdh);
extern void bcmsdh_reject_ioreqs(void *sdh, bool reject);
extern bool bcmsdh_fatal_error(void *sdh);
static int dhdsdio_suspend(void *context);
static int dhdsdio_resume(void *context);

#ifndef DHDSDIO_MEM_DUMP_FNAME
#define DHDSDIO_MEM_DUMP_FNAME "mem_dump"
#endif // endif

#define QLEN (1024) /* bulk rx and tx queue lengths */
#define FCHI (QLEN - 10)
#define FCLOW (FCHI / 2)
#define PRIOMASK 0x7

#define F0_BLOCK_SIZE 32
#define TXRETRIES 2 /* # of retries for tx frames */
#define READ_FRM_CNT_RETRIES 3
#ifndef DHD_RXBOUND
#define DHD_RXBOUND 50 /* Default for max rx frames in one scheduling */
#endif                 // endif

#ifndef DHD_TXBOUND
#define DHD_TXBOUND 20 /* Default for max tx frames in one scheduling */
#endif                 // endif

#define DHD_TXMINMAX 1 /* Max tx frames if rx still pending */

#define MEMBLOCK 2048 /* Block size used for downloading of dongle image */
#define MAX_MEMBLOCK                                                           \
    (32 * 1024) /* Block size used for downloading of dongle image */

#define MAX_DATA_BUF                                                           \
    (0x40 * 1024) /* Must be large enough to hold biggest possible glom */
#define MAX_MEM_BUF 4096

#ifndef DHD_FIRSTREAD
#define DHD_FIRSTREAD 32
#endif // endif
#if !ISPOWEROF2(DHD_FIRSTREAD)
#error DHD_FIRSTREAD is not a power of 2!
#endif // endif

/* Total length of frame header for dongle protocol */
#define SDPCM_HDRLEN (SDPCM_FRAMETAG_LEN + SDPCM_SWHEADER_LEN)
#define SDPCM_HDRLEN_TXGLOM (SDPCM_HDRLEN + SDPCM_HWEXT_LEN)
#define MAX_TX_PKTCHAIN_CNT SDPCM_MAXGLOM_SIZE

#ifdef SDTEST
#define SDPCM_RESERVE (SDPCM_HDRLEN + SDPCM_TEST_HDRLEN + DHD_SDALIGN)
#else
#define SDPCM_RESERVE (SDPCM_HDRLEN + DHD_SDALIGN)
#endif // endif

/* Space for header read, limit for data packets */
#ifndef MAX_HDR_READ
#define MAX_HDR_READ 32
#endif // endif
#if !ISPOWEROF2(MAX_HDR_READ)
#error MAX_HDR_READ is not a power of 2!
#endif // endif

#define MAX_RX_DATASZ 2048

/* Maximum milliseconds to wait for F2 to come up */
#define DHD_WAIT_F2RDY 3000

/* Maximum usec to wait for HTAVAIL to come up */
#define DHD_WAIT_HTAVAIL 10000

/* Bump up limit on waiting for HT to account for first startup;
 * if the image is doing a CRC calculation before programming the PMU
 * for HT availability, it could take a couple hundred ms more, so
 * max out at a 1 second (1000000us).
 */
#if (PMU_MAX_TRANSITION_DLY <= 1000000)
#undef PMU_MAX_TRANSITION_DLY
#ifdef NO_EXT32K
#define PMU_MAX_TRANSITION_DLY (1000000 * 5)
#else
#define PMU_MAX_TRANSITION_DLY 1000000
#endif
#endif // endif

/* hooks for limiting threshold custom tx num in rx processing */
#define DEFAULT_TXINRX_THRES 0
#ifndef CUSTOM_TXINRX_THRES
#define CUSTOM_TXINRX_THRES DEFAULT_TXINRX_THRES
#endif // endif

/* Value for ChipClockCSR during initial setup */
#define DHD_INIT_CLKCTL1 (SBSDIO_FORCE_HW_CLKREQ_OFF | SBSDIO_ALP_AVAIL_REQ)
#define DHD_INIT_CLKCTL2 (SBSDIO_FORCE_HW_CLKREQ_OFF | SBSDIO_FORCE_ALP)

/* Flags for SDH calls */
#define F2SYNC (SDIO_REQ_4BYTE | SDIO_REQ_FIXED)

/* Packet free applicable unconditionally for sdio and sdspi.  Conditional if
 * bufpool was present for gspi bus.
 */
#define PKTFREE2()                                                             \
    if ((bus->bus != SPI_BUS) || bus->usebufpool)                              \
        PKTFREE(bus->dhd->osh, pkt, FALSE);
DHD_SPINWAIT_SLEEP_INIT(sdioh_spinwait_sleep);

#ifdef SUPPORT_MULTIPLE_BOARD_REV_FROM_HW
extern unsigned int system_hw_rev;
#endif /* SUPPORT_MULTIPLE_BOARD_REV_FROM_HW */

/* Device console log buffer state */
#define CONSOLE_LINE_MAX 192
#define CONSOLE_BUFFER_MAX 8192
typedef struct dhd_console {
    uint count;    /* Poll interval msec counter */
    uint log_addr; /* Log struct address (fixed) */
    hnd_log_t log; /* Log struct (host copy) */
    uint bufsize;  /* Size of log buffer */
    uint8 *buf;    /* Log buffer (host copy) */
    uint last;     /* Last buffer read index */
} dhd_console_t;

#define REMAP_ENAB(bus) ((bus)->remap)
#define REMAP_ISADDR(bus, a)                                                   \
    (((a) >= ((bus)->orig_ramsize)) && ((a) < ((bus)->ramsize)))
#define KSO_ENAB(bus) ((bus)->kso)
#define SR_ENAB(bus) ((bus)->_srenab)
#define SLPAUTO_ENAB(bus) ((SR_ENAB(bus)) && ((bus)->_slpauto))

#define MIN_RSRC_SR 0x3
#define CORE_CAPEXT_ADDR_OFFSET (0x64c)
#define CORE_CAPEXT_SR_SUPPORTED_MASK (1 << 1)
#define RCTL_MACPHY_DISABLE_MASK (1 << 26)
#define RCTL_LOGIC_DISABLE_MASK (1 << 27)

#define OOB_WAKEUP_ENAB(bus) ((bus)->_oobwakeup)
#define GPIO_DEV_SRSTATE 16 /* Host gpio17 mapped to device gpio0 SR state */
#define GPIO_DEV_SRSTATE_TIMEOUT 320000 /* 320ms */
#define GPIO_DEV_WAKEUP 17 /* Host gpio17 mapped to device gpio1 wakeup */
#define CC_CHIPCTRL2_GPIO1_WAKEUP (1 << 0)
#define CC_CHIPCTRL3_SR_ENG_ENABLE (1 << 2)
#define OVERFLOW_BLKSZ512_WM 96
#define OVERFLOW_BLKSZ512_MES 80

#define CC_PMUCC3 (0x3)

#ifdef DHD_UCODE_DOWNLOAD
/* Ucode host download related macros */
#define UCODE_DOWNLOAD_REQUEST 0xCAFECAFE
#define UCODE_DOWNLOAD_COMPLETE 0xABCDABCD
#endif /* DHD_UCODE_DOWNLOAD */

#if defined(BT_OVER_SDIO)
#define BTMEM_OFFSET 0x19000000
/* BIT0 => WLAN Power UP and BIT1=> WLAN Wake */
#define BT2WLAN_PWRUP_WAKE 0x03
#define BT2WLAN_PWRUP_ADDR 0x640894 /* This address is specific to 43012B0 */

#define BTFW_MAX_STR_LEN 600
#define BTFW_DOWNLOAD_BLK_SIZE (BTFW_MAX_STR_LEN / 2 + 0x8)

#define BTFW_ADDR_MODE_UNKNOWN 0
#define BTFW_ADDR_MODE_EXTENDED 1
#define BTFW_ADDR_MODE_SEGMENT 2
#define BTFW_ADDR_MODE_LINEAR32 3

#define BTFW_HEX_LINE_TYPE_DATA 0
#define BTFW_HEX_LINE_TYPE_END_OF_DATA 1
#define BTFW_HEX_LINE_TYPE_EXTENDED_SEGMENT_ADDRESS 2
#define BTFW_HEX_LINE_TYPE_EXTENDED_ADDRESS 4
#define BTFW_HEX_LINE_TYPE_ABSOLUTE_32BIT_ADDRESS 5

#endif /* defined (BT_OVER_SDIO) */

/* Private data for SDIO bus interaction */
typedef struct dhd_bus {
    dhd_pub_t *dhd;

    bcmsdh_info_t *sdh; /* Handle for BCMSDH calls */
    si_t *sih;          /* Handle for SI calls */
    char *vars;         /* Variables (from CIS and/or other) */
    uint varsz;         /* Size of variables buffer */
    uint32 sbaddr;      /* Current SB window pointer (-1, invalid) */

    sdpcmd_regs_t *regs; /* Registers for SDIO core */
    uint sdpcmrev;       /* SDIO core revision */
    uint armrev;         /* CPU core revision */
    uint ramrev;         /* SOCRAM core revision */
    uint32 ramsize;      /* Size of RAM in SOCRAM (bytes) */
    uint32 orig_ramsize; /* Size of RAM in SOCRAM (bytes) */
    uint32 srmemsize;    /* Size of SRMEM */

    uint32 bus;         /* gSPI or SDIO bus */
    uint32 bus_num;     /* bus number */
    uint32 slot_num;    /* slot ID */
    uint32 hostintmask; /* Copy of Host Interrupt Mask */
    uint32 intstatus;   /* Intstatus bits (events) pending */
    bool dpc_sched;     /* Indicates DPC schedule (intrpt rcvd) */
    bool fcstate;       /* State of dongle flow-control */

    uint16 cl_devid; /* cached devid for dhdsdio_probe_attach() */
    char *fw_path;   /* module_param: path to firmware image */
    char *nv_path;   /* module_param: path to nvram vars file */

    uint blocksize; /* Block size of SDIO transfers */
    uint roundup;   /* Max roundup limit */

    struct pktq txq;   /* Queue length used for flow-control */
    uint8 flowcontrol; /* per prio flow control bitmask */
    uint8 tx_seq;      /* Transmit sequence number (next) */
    uint8 tx_max;      /* Maximum transmit sequence allowed */

#ifdef DYNAMIC_MAX_HDR_READ
    uint8 *hdrbufp;
#else
    uint8 hdrbuf[MAX_HDR_READ + DHD_SDALIGN];
#endif
    uint8 *rxhdr;   /* Header of current rx frame (in hdrbuf) */
    uint16 nextlen; /* Next Read Len from last header */
    uint8 rx_seq;   /* Receive sequence number (expected) */
    bool rxskip;    /* Skip receive (awaiting NAK ACK) */

    void *glomd;  /* Packet containing glomming descriptor */
    void *glom;   /* Packet chain for glommed superframe */
    uint glomerr; /* Glom packet read errors */

    uint8 *rxbuf;   /* Buffer for receiving control packets */
    uint rxblen;    /* Allocated length of rxbuf */
    uint8 *rxctl;   /* Aligned pointer into rxbuf */
    uint8 *databuf; /* Buffer for receiving big glom packet */
    uint8 *dataptr; /* Aligned pointer into databuf */
    uint rxlen;     /* Length of valid data in buffer */

    uint8 sdpcm_ver; /* Bus protocol reported by dongle */

    bool intr;      /* Use interrupts */
    bool poll;      /* Use polling */
    bool ipend;     /* Device interrupt is pending */
    bool intdis;    /* Interrupts disabled by isr */
    uint intrcount; /* Count of device interrupt callbacks */
    uint lastintrs; /* Count as of last watchdog timer */
    uint spurious;  /* Count of spurious interrupts */
    uint pollrate;  /* Ticks between device polls */
    uint polltick;  /* Tick counter */
    uint pollcnt;   /* Count of active polls */

    dhd_console_t console; /* Console output polling support */
    uint console_addr;     /* Console address from shared struct */

    uint regfails; /* Count of R_REG/W_REG failures */

    uint clkstate;    /* State of sd and backplane clock(s) */
    bool activity;    /* Activity flag for clock down */
    int32 idletime;   /* Control for activity timeout */
    int32 idlecount;  /* Activity timeout counter */
    int32 idleclock;  /* How to set bus driver when idle */
    int32 sd_divisor; /* Speed control to bus driver */
    int32 sd_mode;    /* Mode control to bus driver */
    int32 sd_rxchain; /* If bcmsdh api accepts PKT chains */
    bool use_rxchain; /* If dhd should use PKT chains */
    bool sleeping;    /* Is SDIO bus sleeping? */
#if defined(SUPPORT_P2P_GO_PS)
    wait_queue_head_t bus_sleep;
#endif /* LINUX && SUPPORT_P2P_GO_PS */
    bool ctrl_wait;
    wait_queue_head_t ctrl_tx_wait;
    uint rxflow_mode;    /* Rx flow control mode */
    bool rxflow;         /* Is rx flow control on */
    uint prev_rxlim_hit; /* Is prev rx limit exceeded (per dpc schedule) */
    bool alp_only;       /* Don't use HT clock (ALP only) */
    /* Field to decide if rx of control frames happen in rxbuf or lb-pool */
    bool usebufpool;
    int32 txinrx_thres; /* num of in-queued pkts */
    int32 dotxinrx;     /* tx first in dhdsdio_readframes */
#ifdef BCMSDIO_RXLIM_POST
    bool rxlim_en;
    uint32 rxlim_addr;
#endif /* BCMSDIO_RXLIM_POST */
#ifdef SDTEST
    /* external loopback */
    bool ext_loop;
    uint8 loopid;

    /* pktgen configuration */
    uint pktgen_freq;   /* Ticks between bursts */
    uint pktgen_count;  /* Packets to send each burst */
    uint pktgen_print;  /* Bursts between count displays */
    uint pktgen_total;  /* Stop after this many */
    uint pktgen_minlen; /* Minimum packet data len */
    uint pktgen_maxlen; /* Maximum packet data len */
    uint pktgen_mode;   /* Configured mode: tx, rx, or echo */
    uint pktgen_stop;   /* Number of tx failures causing stop */

    /* active pktgen fields */
    uint pktgen_tick;      /* Tick counter for bursts */
    uint pktgen_ptick;     /* Burst counter for printing */
    uint pktgen_sent;      /* Number of test packets generated */
    uint pktgen_rcvd;      /* Number of test packets received */
    uint pktgen_prev_time; /* Time at which previous stats where printed */
    uint pktgen_prev_sent; /* Number of test packets generated when
                            * previous stats were printed
                            */
    uint pktgen_prev_rcvd; /* Number of test packets received when
                            * previous stats were printed
                            */
    uint pktgen_fail;      /* Number of failed send attempts */
    uint16 pktgen_len;     /* Length of next packet to send */
#define PKTGEN_RCV_IDLE (0)
#define PKTGEN_RCV_ONGOING (1)
    uint16 pktgen_rcv_state;     /* receive state */
    uint pktgen_rcvd_rcvsession; /* test pkts rcvd per rcv session. */
#endif                           /* SDTEST */

    /* Some additional counters */
    uint tx_sderrs;            /* Count of tx attempts with sd errors */
    uint fcqueued;             /* Tx packets that got queued */
    uint rxrtx;                /* Count of rtx requests (NAK to dongle) */
    uint rx_toolong;           /* Receive frames too long to receive */
    uint rxc_errors;           /* SDIO errors when reading control frames */
    uint rx_hdrfail;           /* SDIO errors on header reads */
    uint rx_badhdr;            /* Bad received headers (roosync?) */
    uint rx_badseq;            /* Mismatched rx sequence number */
    uint fc_rcvd;              /* Number of flow-control events received */
    uint fc_xoff;              /* Number which turned on flow-control */
    uint fc_xon;               /* Number which turned off flow-control */
    uint rxglomfail;           /* Failed deglom attempts */
    uint rxglomframes;         /* Number of glom frames (superframes) */
    uint rxglompkts;           /* Number of packets from glom frames */
    uint f2rxhdrs;             /* Number of header reads */
    uint f2rxdata;             /* Number of frame data reads */
    uint f2txdata;             /* Number of f2 frame writes */
    uint f1regdata;            /* Number of f1 register accesses */
    wake_counts_t wake_counts; /* Wake up counter */
#ifdef BCMSPI
    bool dwordmode;
#endif /* BCMSPI */
#ifdef DHDENABLE_TAILPAD
    uint tx_tailpad_chain;  /* Number of tail padding by chaining pad_pkt */
    uint tx_tailpad_pktget; /* Number of tail padding by new PKTGET */
#endif                      /* DHDENABLE_TAILPAD */
    uint8 *ctrl_frame_buf;
    uint32 ctrl_frame_len;
    bool ctrl_frame_stat;
#ifndef BCMSPI
    uint32 rxint_mode; /* rx interrupt mode */
#endif                 /* BCMSPI */
    bool remap;        /* Contiguous 1MB RAM: 512K socram + 512K devram
                        * Available with socram rev 16
                        * Remap region not DMA-able
                        */
    bool kso;
    bool _slpauto;
    bool _oobwakeup;
    bool _srenab;
    bool readframes;
    bool reqbussleep;
    uint32 resetinstr;
    uint32 dongle_ram_base;

    void *glom_pkt_arr[SDPCM_MAXGLOM_SIZE]; /* Array of pkts for glomming */
    uint32 txglom_cnt;       /* Number of pkts in the glom array */
    uint32 txglom_total_len; /* Total length of pkts in glom array */
    bool txglom_enable; /* Flag to indicate whether tx glom is enabled/disabled
                         */
    uint32 txglomsize;  /* Glom size limitation */
#ifdef DHDENABLE_TAILPAD
    void *pad_pkt;
#endif                       /* DHDENABLE_TAILPAD */
    uint32 dongle_trap_addr; /* device trap addr location in device memory */
#if defined(BT_OVER_SDIO)
    char *btfw_path;     /* module_param: path to BT firmware image */
    uint32 bt_use_count; /* Counter that tracks whether BT is using the bus */
#endif                   /* defined (BT_OVER_SDIO) */
    uint txglomframes;   /* Number of tx glom frames (superframes) */
    uint txglompkts;     /* Number of packets from tx glom frames */
#ifdef PKT_STATICS
    struct pkt_statics tx_statics;
#endif
    uint8 *membuf; /* Buffer for dhdsdio_membytes */
#ifdef CONSOLE_DPC
    char cons_cmd[0x10];
#endif
} dhd_bus_t;

/*
 * Whenever DHD_IDLE_IMMEDIATE condition is handled, we have to now check if
 * BT is active too. Instead of adding #ifdef code in all the places, we thought
 * of adding one macro check as part of the if condition that checks for
 * DHD_IDLE_IMMEDIATE In case of non BT over SDIO builds, this macro will always
 * return TRUE. In case of the builds where BT_OVER_SDIO is enabled, it will
 * expand to a condition check that checks if bt_use_count is zero. So this
 * macro will return equate to 1 if bt_use_count is 0, indicating that there are
 * no active users and if bt_use_count is non zero it would return 0 there by
 * preventing the caller from executing the sleep calls.
 */
#ifdef BT_OVER_SDIO
#define NO_OTHER_ACTIVE_BUS_USER(bus) (bus->bt_use_count == 0)
#else
#define NO_OTHER_ACTIVE_BUS_USER(bus) (1)
#endif /* BT_OVER_SDIO */

/* clkstate */
#define CLK_NONE 0
#define CLK_SDONLY 1
#define CLK_PENDING 2 /* Not used yet */
#define CLK_AVAIL 3

#define DHD_NOPMU(dhd) (FALSE)

#if defined(BCMSDIOH_STD)
#define BLK_64_MAXTXGLOM 20
#endif /* BCMSDIOH_STD */

#ifdef DHD_DEBUG
static int qcount[NUMPRIO];
static int tx_packets[NUMPRIO];
#endif /* DHD_DEBUG */

/* Deferred transmit */
const uint dhd_deferred_tx = 1;

extern uint dhd_watchdog_ms;
extern uint sd_f1_blocksize;

extern void dhd_os_wd_timer(void *bus, uint wdtick);
int dhd_enableOOB(dhd_pub_t *dhd, bool sleep);

#ifdef DHD_PM_CONTROL_FROM_FILE
extern bool g_pm_control;
#endif /* DHD_PM_CONTROL_FROM_FILE */

/* Tx/Rx bounds */
uint dhd_txbound;
uint dhd_rxbound;
uint dhd_txminmax = DHD_TXMINMAX;

/* override the RAM size if possible */
#define DONGLE_MIN_RAMSIZE (128 * 1024)
int dhd_dongle_ramsize;

uint dhd_doflow = TRUE;
uint dhd_dpcpoll = FALSE;

module_param(dhd_doflow, uint, 0644);
module_param(dhd_dpcpoll, uint, 0644);

static bool dhd_alignctl;

static bool sd1idle;

static bool retrydata;
#define RETRYCHAN(chan) (((chan) == SDPCM_EVENT_CHANNEL) || retrydata)

#ifdef BCMSPI
/* At a watermark around 8 the spid hits underflow error. */
static uint watermark = 32;
static uint mesbusyctrl = 0;
#else
static uint watermark = 8;
static uint mesbusyctrl = 0;
#endif /* BCMSPI */
#ifdef DYNAMIC_MAX_HDR_READ
uint firstread = DHD_FIRSTREAD;
#else
static const uint firstread = DHD_FIRSTREAD;
#endif

/* Retry count for register access failures */
static const uint retry_limit = 2;

/* Force even SD lengths (some host controllers mess up on odd bytes) */
static bool forcealign;

#if defined(DEBUGGER)
static uint32 dhd_sdio_reg_read(struct dhd_bus *bus, ulong addr);
static void dhd_sdio_reg_write(struct dhd_bus *bus, ulong addr, uint32 val);

/** the debugger layer will call back into this (bus) layer to read/write dongle
 * memory */
static struct dhd_dbg_bus_ops_s bus_ops = {
    .read_u16 = NULL,
    .read_u32 = dhd_sdio_reg_read,
    .write_u32 = dhd_sdio_reg_write,
};
#endif /* DEBUGGER */

#define ALIGNMENT 4

#if (defined(OOB_INTR_ONLY) && defined(HW_OOB)) || defined(FORCE_WOWLAN)
extern void bcmsdh_enable_hw_oob_intr(void *sdh, bool enable);
#endif // endif

#if defined(OOB_INTR_ONLY) && defined(SDIO_ISR_THREAD)
#error OOB_INTR_ONLY is NOT working with SDIO_ISR_THREAD
#endif /* defined(OOB_INTR_ONLY) && defined(SDIO_ISR_THREAD) */
#define PKTALIGN(osh, p, len, align)                                           \
    do {                                                                       \
        uintptr datalign;                                                      \
        datalign = (uintptr)PKTDATA((osh), (p));                               \
        datalign = ROUNDUP(datalign, (align)) - datalign;                      \
        ASSERT(datalign < (align));                                            \
        ASSERT(PKTLEN((osh), (p)) >= ((len) + datalign));                      \
        if (datalign)                                                          \
            PKTPULL((osh), (p), (uint)datalign);                               \
        PKTSETLEN((osh), (p), (len));                                          \
    } while (0)

/* Limit on rounding up frames */
static const uint max_roundup = 512;

/* Try doing readahead */
static bool dhd_readahead;

#if defined(BCMSDIOH_TXGLOM_EXT)
bool dhdsdio_is_dataok(dhd_bus_t *bus)
{
    return (
        ((uint8)(bus->tx_max - bus->tx_seq) - bus->dhd->conf->tx_max_offset >
         1) &&
        (((uint8)(bus->tx_max - bus->tx_seq) & 0x80) == 0));
}

uint8 dhdsdio_get_databufcnt(dhd_bus_t *bus)
{
    return ((uint8)(bus->tx_max - bus->tx_seq) - 1 -
            bus->dhd->conf->tx_max_offset);
}
#endif

/* To check if there's window offered */
#if defined(BCMSDIOH_TXGLOM_EXT)
#define DATAOK(bus) dhdsdio_is_dataok(bus)
#else
#define DATAOK(bus)                                                            \
    (((uint8)(bus->tx_max - bus->tx_seq) > 1) &&                               \
     (((uint8)(bus->tx_max - bus->tx_seq) & 0x80) == 0))
#endif

/* To check if there's window offered for ctrl frame */
#define TXCTLOK(bus)                                                           \
    (((uint8)(bus->tx_max - bus->tx_seq) != 0) &&                              \
     (((uint8)(bus->tx_max - bus->tx_seq) & 0x80) == 0))

/* Number of pkts available in dongle for data RX */
#if defined(BCMSDIOH_TXGLOM_EXT)
#define DATABUFCNT(bus) dhdsdio_get_databufcnt(bus)
#else
#define DATABUFCNT(bus) ((uint8)(bus->tx_max - bus->tx_seq) - 1)
#endif

/* Macros to get register read/write status */
/* NOTE: these assume a local dhdsdio_bus_t *bus! */
#define R_SDREG(regvar, regaddr, retryvar)                                     \
    do {                                                                       \
        retryvar = 0;                                                          \
        do {                                                                   \
            regvar = R_REG(bus->dhd->osh, regaddr);                            \
        } while (bcmsdh_regfail(bus->sdh) && (++retryvar <= retry_limit));     \
        if (retryvar) {                                                        \
            bus->regfails += (retryvar - 1);                                   \
            if (retryvar > retry_limit) {                                      \
                DHD_ERROR(("%s: FAILED" #regvar "READ, LINE %d\n",             \
                           __FUNCTION__, __LINE__));                           \
                regvar = 0;                                                    \
            }                                                                  \
        }                                                                      \
    } while (0)

#define W_SDREG(regval, regaddr, retryvar)                                     \
    do {                                                                       \
        retryvar = 0;                                                          \
        do {                                                                   \
            W_REG(bus->dhd->osh, regaddr, regval);                             \
        } while (bcmsdh_regfail(bus->sdh) && (++retryvar <= retry_limit));     \
        if (retryvar) {                                                        \
            bus->regfails += (retryvar - 1);                                   \
            if (retryvar > retry_limit)                                        \
                DHD_ERROR(("%s: FAILED REGISTER WRITE, LINE %d\n",             \
                           __FUNCTION__, __LINE__));                           \
        }                                                                      \
    } while (0)

#define BUS_WAKE(bus)                                                          \
    do {                                                                       \
        bus->idlecount = 0;                                                    \
        if ((bus)->sleeping)                                                   \
            dhdsdio_bussleep((bus), FALSE);                                    \
    } while (0);

/*
 * pktavail interrupts from dongle to host can be managed in 3 different ways
 * whenever there is a packet available in dongle to transmit to host.
 *
 * Mode 0:	Dongle writes the software host mailbox and host is interrupted.
 * Mode 1:	(sdiod core rev >= 4)
 *		Device sets a new bit in the intstatus whenever there is a packet
 *		available in fifo.  Host can't clear this specific status bit until all
 * the packets are read from the FIFO.  No need to ack dongle intstatus. Mode 2:
 * (sdiod core rev >= 4) Device sets a bit in the intstatus, and host acks this
 * by writing one to this bit.  Dongle won't generate anymore packet interrupts
 *		until host reads all the packets from the dongle and reads a zero to
 *		figure that there are no more packets.  No need to disable host ints.
 *		Need to ack the intstatus.
 */

#define SDIO_DEVICE_HMB_RXINT 0        /* default old way */
#define SDIO_DEVICE_RXDATAINT_MODE_0 1 /* from sdiod rev 4 */
#define SDIO_DEVICE_RXDATAINT_MODE_1 2 /* from sdiod rev 4 */

#ifdef BCMSPI

#define FRAME_AVAIL_MASK(bus) I_HMB_FRAME_IND

#define DHD_BUS SPI_BUS

/* check packet-available-interrupt in piggybacked dstatus */
#define PKT_AVAILABLE(bus, intstatus)                                          \
    (bcmsdh_get_dstatus(bus->sdh) & STATUS_F2_PKT_AVAILABLE)

#define HOSTINTMASK (I_HMB_FC_CHANGE | I_HMB_HOST_INT)

#define GSPI_PR55150_BAILOUT                                                   \
    do {                                                                       \
        uint32 dstatussw = bcmsdh_get_dstatus((void *)bus->sdh);               \
        uint32 dstatushw = bcmsdh_cfg_read_word(bus->sdh, SDIO_FUNC_0,         \
                                                SPID_STATUS_REG, NULL);        \
        uint32 intstatuserr = 0;                                               \
        uint retries = 0;                                                      \
                                                                               \
        R_SDREG(intstatuserr, &bus->regs->intstatus, retries);                 \
        printf("dstatussw = 0x%x, dstatushw = 0x%x, intstatus = 0x%x\n",       \
               dstatussw, dstatushw, intstatuserr);                            \
                                                                               \
        bus->nextlen = 0;                                                      \
        *finished = TRUE;                                                      \
    } while (0)

#else /* BCMSDIO */

#define FRAME_AVAIL_MASK(bus)                                                  \
    ((bus->rxint_mode == SDIO_DEVICE_HMB_RXINT) ? I_HMB_FRAME_IND              \
                                                : I_XMTDATA_AVAIL)

#define DHD_BUS SDIO_BUS

#define PKT_AVAILABLE(bus, intstatus) ((intstatus) & (FRAME_AVAIL_MASK(bus)))

#define HOSTINTMASK (I_HMB_SW_MASK | I_CHIPACTIVE)

#define GSPI_PR55150_BAILOUT

#endif /* BCMSPI */

#ifdef SDTEST
static void dhdsdio_testrcv(dhd_bus_t *bus, void *pkt, uint seq);
static void dhdsdio_sdtest_set(dhd_bus_t *bus, uint count);
#endif // endif

static int dhdsdio_checkdied(dhd_bus_t *bus, char *data, uint size);
#ifdef DHD_DEBUG
static int dhd_serialconsole(dhd_bus_t *bus, bool get, bool enable,
                             int *bcmerror);
#endif /* DHD_DEBUG */

#if defined(DHD_FW_COREDUMP)
static int dhdsdio_mem_dump(dhd_bus_t *bus);
static int dhdsdio_get_mem_dump(dhd_bus_t *bus);
#endif /* DHD_FW_COREDUMP */
static int dhdsdio_devcap_set(dhd_bus_t *bus, uint8 cap);
static int dhdsdio_download_state(dhd_bus_t *bus, bool enter);

static void dhdsdio_release(dhd_bus_t *bus, osl_t *osh);
static void dhdsdio_release_malloc(dhd_bus_t *bus, osl_t *osh);
static void dhdsdio_disconnect(void *ptr);
static bool dhdsdio_chipmatch(uint16 chipid);
static bool dhdsdio_probe_attach(dhd_bus_t *bus, osl_t *osh, void *sdh,
                                 void *regsva, uint16 devid);
static bool dhdsdio_probe_malloc(dhd_bus_t *bus, osl_t *osh, void *sdh);
static bool dhdsdio_probe_init(dhd_bus_t *bus, osl_t *osh, void *sdh);
static void dhdsdio_release_dongle(dhd_bus_t *bus, osl_t *osh,
                                   bool dongle_isolation, bool reset_flag);

static void dhd_dongle_setramsize(struct dhd_bus *bus, int mem_size);
static int dhd_bcmsdh_recv_buf(dhd_bus_t *bus, uint32 addr, uint fn, uint flags,
                               uint8 *buf, uint nbytes, void *pkt,
                               bcmsdh_cmplt_fn_t complete, void *handle);
static int dhd_bcmsdh_send_buf(dhd_bus_t *bus, uint32 addr, uint fn, uint flags,
                               uint8 *buf, uint nbytes, void *pkt,
                               bcmsdh_cmplt_fn_t complete, void *handle,
                               int max_retry);
static int dhdsdio_txpkt(dhd_bus_t *bus, uint chan, void **pkts, int num_pkt,
                         bool free_pkt);
static int dhdsdio_txpkt_preprocess(dhd_bus_t *bus, void *pkt, int chan,
                                    int txseq, int prev_chain_total_len,
                                    bool last_chained_pkt, int *pad_pkt_len,
                                    void **new_pkt
#if defined(BCMSDIOH_TXGLOM_EXT)
                                    ,
                                    int first_frame
#endif
);
static int dhdsdio_txpkt_postprocess(dhd_bus_t *bus, void *pkt);

static int dhdsdio_download_firmware(dhd_bus_t *bus, osl_t *osh, void *sdh);
static int _dhdsdio_download_firmware(dhd_bus_t *bus);

#ifdef DHD_UCODE_DOWNLOAD
static int dhdsdio_download_ucode_file(struct dhd_bus *bus, char *ucode_path);
#endif /* DHD_UCODE_DOWNLOAD */
static int dhdsdio_download_code_file(dhd_bus_t *bus, char *image_path);
static int dhdsdio_download_nvram(dhd_bus_t *bus);
static int dhdsdio_bussleep(dhd_bus_t *bus, bool sleep);
static int dhdsdio_clkctl(dhd_bus_t *bus, uint target, bool pendok);
static uint8 dhdsdio_sleepcsr_get(dhd_bus_t *bus);
static bool dhdsdio_dpc(dhd_bus_t *bus);
static int dhd_bcmsdh_send_buffer(void *bus, uint8 *frame, uint16 len);
static int dhdsdio_set_sdmode(dhd_bus_t *bus, int32 sd_mode);
static int dhdsdio_sdclk(dhd_bus_t *bus, bool on);
static void dhdsdio_advertise_bus_cleanup(dhd_pub_t *dhdp);
static void dhdsdio_advertise_bus_remove(dhd_pub_t *dhdp);

#if defined(BT_OVER_SDIO)
static int extract_hex_field(char *line, uint16 start_pos, uint16 num_chars,
                             uint16 *value);
static int read_more_btbytes(struct dhd_bus *bus, void *file, char *line,
                             int *addr_mode, uint16 *hi_addr, uint32 *dest_addr,
                             uint8 *data_bytes, uint32 *num_bytes);
static int dhdsdio_download_btfw(struct dhd_bus *bus, osl_t *osh, void *sdh);
static int _dhdsdio_download_btfw(struct dhd_bus *bus);
#endif /* defined (BT_OVER_SDIO) */

#ifdef DHD_ULP
#include <dhd_ulp.h>
static int dhd_bus_ulp_reinit_fw(dhd_bus_t *bus);
#endif /* DHD_ULP */

#ifdef DHD_WAKE_STATUS
int bcmsdh_get_total_wake(bcmsdh_info_t *bcmsdh);
int bcmsdh_set_get_wake(bcmsdh_info_t *bcmsdh, int flag);
#endif /* DHD_WAKE_STATUS */

static void dhdsdio_tune_fifoparam(struct dhd_bus *bus)
{
    int err;
    uint8 devctl, wm, mes;

    if (bus->sih->buscorerev >= 0xF) {
        /* See .ppt in PR for these recommended values */
        if (bus->blocksize == 0x200) {
            wm = OVERFLOW_BLKSZ512_WM;
            mes = OVERFLOW_BLKSZ512_MES;
        } else {
            mes = bus->blocksize / 0x4;
            wm = bus->blocksize / 0x4;
        }

        watermark = wm;
        mesbusyctrl = mes;
    } else {
        DHD_INFO(
            ("skip fifotune: SdioRev(%d) is lower than minimal requested ver\n",
             bus->sih->buscorerev));
        return;
    }

    /* Update watermark */
    if (wm > 0) {
        bcmsdh_cfg_write(bus->sdh, SDIO_FUNC_1, SBSDIO_WATERMARK, wm, &err);

        devctl =
            bcmsdh_cfg_read(bus->sdh, SDIO_FUNC_1, SBSDIO_DEVICE_CTL, &err);
        devctl |= SBSDIO_DEVCTL_F2WM_ENAB;
        bcmsdh_cfg_write(bus->sdh, SDIO_FUNC_1, SBSDIO_DEVICE_CTL, devctl,
                         &err);
    }

    /* Update MES */
    if (mes > 0) {
        bcmsdh_cfg_write(bus->sdh, SDIO_FUNC_1, SBSDIO_FUNC1_MESBUSYCTRL,
                         (mes | SBSDIO_MESBUSYCTRL_ENAB), &err);
    }

    DHD_INFO(("Apply overflow WAR: 0x%02x 0x%02x 0x%02x\n",
              bcmsdh_cfg_read(bus->sdh, SDIO_FUNC_1, SBSDIO_DEVICE_CTL, &err),
              bcmsdh_cfg_read(bus->sdh, SDIO_FUNC_1, SBSDIO_WATERMARK, &err),
              bcmsdh_cfg_read(bus->sdh, SDIO_FUNC_1, SBSDIO_FUNC1_MESBUSYCTRL,
                              &err)));
}

static void dhd_dongle_setramsize(struct dhd_bus *bus, int mem_size)
{
    int32 min_size = DONGLE_MIN_RAMSIZE;
    /* Restrict the ramsize to user specified limit */
    DHD_ERROR(("user: Restrict the dongle ram size to %d, min accepted %d\n",
               dhd_dongle_ramsize, min_size));
    if ((dhd_dongle_ramsize > min_size) &&
        (dhd_dongle_ramsize < (int32)bus->orig_ramsize)) {
        bus->ramsize = dhd_dongle_ramsize;
    }
}

static int dhdsdio_set_siaddr_window(dhd_bus_t *bus, uint32 address)
{
    int err = 0;
    bcmsdh_cfg_write(bus->sdh, SDIO_FUNC_1, SBSDIO_FUNC1_SBADDRLOW,
                     (address >> 0x8) & SBSDIO_SBADDRLOW_MASK, &err);
    if (!err) {
        bcmsdh_cfg_write(bus->sdh, SDIO_FUNC_1, SBSDIO_FUNC1_SBADDRMID,
                         (address >> 0x10) & SBSDIO_SBADDRMID_MASK, &err);
    }
    if (!err) {
        bcmsdh_cfg_write(bus->sdh, SDIO_FUNC_1, SBSDIO_FUNC1_SBADDRHIGH,
                         (address >> 0x18) & SBSDIO_SBADDRHIGH_MASK, &err);
    }
    return err;
}

#ifdef BCMSPI
static void dhdsdio_wkwlan(dhd_bus_t *bus, bool on)
{
    int err;
    uint32 regdata;
    bcmsdh_info_t *sdh = bus->sdh;

    if (bus->sih->buscoretype == SDIOD_CORE_ID) {
        /* wake up wlan function :WAKE_UP goes as ht_avail_request and
         * alp_avail_request */
        regdata = bcmsdh_cfg_read_word(sdh, SDIO_FUNC_0, SPID_CONFIG, NULL);
        DHD_INFO(("F0 REG0 rd = 0x%x\n", regdata));

        if (on == TRUE) {
            regdata |= WAKE_UP;
        } else {
            regdata &= ~WAKE_UP;
        }

        bcmsdh_cfg_write_word(sdh, SDIO_FUNC_0, SPID_CONFIG, regdata, &err);
    }
}
#endif /* BCMSPI */

#ifdef USE_OOB_GPIO1
static int dhdsdio_oobwakeup_init(dhd_bus_t *bus)
{
    uint32 val, addr, data;

    bcmsdh_gpioouten(bus->sdh, GPIO_DEV_WAKEUP);

    addr = SI_ENUM_BASE(bus->sih) + OFFSETOF(chipcregs_t, chipcontrol_addr);
    data = SI_ENUM_BASE(bus->sih) + OFFSETOF(chipcregs_t, chipcontrol_data);

    /* Set device for gpio1 wakeup */
    bcmsdh_reg_write(bus->sdh, addr, 0x4, 0x2);
    val = bcmsdh_reg_read(bus->sdh, data, 0x4);
    val |= CC_CHIPCTRL2_GPIO1_WAKEUP;
    bcmsdh_reg_write(bus->sdh, data, 0x4, val);

    bus->_oobwakeup = TRUE;

    return 0;
}
#endif /* USE_OOB_GPIO1 */

#ifndef BCMSPI
/*
 * Query if FW is in SR mode
 */
static bool dhdsdio_sr_cap(dhd_bus_t *bus)
{
    bool cap = FALSE;
    uint32 core_capext, addr, data;

    if (bus->sih->chip == BCM43430_CHIP_ID ||
        bus->sih->chip == BCM43018_CHIP_ID) {
        /* check if fw initialized sr engine */
        addr = SI_ENUM_BASE(bus->sih) + OFFSETOF(chipcregs_t, sr_control1);
        if (bcmsdh_reg_read(bus->sdh, addr, 0x4) != 0) {
            cap = TRUE;
        }

        return cap;
    }
    if (0) {
        core_capext = FALSE;
    } else if ((bus->sih->chip == BCM4330_CHIP_ID) ||
               (bus->sih->chip == BCM43362_CHIP_ID) ||
               (BCM4347_CHIP(bus->sih->chip))) {
        core_capext = FALSE;
    } else if ((bus->sih->chip == BCM4335_CHIP_ID) ||
               (bus->sih->chip == BCM4339_CHIP_ID) ||
               BCM4345_CHIP(bus->sih->chip) ||
               (bus->sih->chip == BCM4354_CHIP_ID) ||
               (bus->sih->chip == BCM4358_CHIP_ID) ||
               (bus->sih->chip == BCM43569_CHIP_ID) ||
               (bus->sih->chip == BCM4371_CHIP_ID) ||
               (BCM4349_CHIP(bus->sih->chip)) ||
               (bus->sih->chip == BCM4350_CHIP_ID) ||
               (bus->sih->chip == BCM4362_CHIP_ID) ||
               (bus->sih->chip == BCM43012_CHIP_ID) ||
               (bus->sih->chip == BCM43014_CHIP_ID) ||
               (bus->sih->chip == BCM43751_CHIP_ID) ||
               (bus->sih->chip == BCM43752_CHIP_ID)) {
        core_capext = TRUE;
    } else {
        core_capext = bcmsdh_reg_read(
            bus->sdh,
            si_get_pmu_reg_addr(bus->sih, OFFSETOF(chipcregs_t, core_cap_ext)),
            0x4);
        core_capext = (core_capext & CORE_CAPEXT_SR_SUPPORTED_MASK);
    }
    if (!(core_capext)) {
        return FALSE;
    }

    if ((bus->sih->chip == BCM4335_CHIP_ID) ||
        (bus->sih->chip == BCM4339_CHIP_ID) || BCM4345_CHIP(bus->sih->chip) ||
        (bus->sih->chip == BCM4354_CHIP_ID) ||
        (bus->sih->chip == BCM4358_CHIP_ID) ||
        (bus->sih->chip == BCM43569_CHIP_ID) ||
        (bus->sih->chip == BCM4371_CHIP_ID) ||
        (bus->sih->chip == BCM4350_CHIP_ID)) {
        uint32 enabval = 0;
        addr = SI_ENUM_BASE(bus->sih) + OFFSETOF(chipcregs_t, chipcontrol_addr);
        data = SI_ENUM_BASE(bus->sih) + OFFSETOF(chipcregs_t, chipcontrol_data);
        bcmsdh_reg_write(bus->sdh, addr, 0x4, CC_PMUCC3);
        enabval = bcmsdh_reg_read(bus->sdh, data, 0x4);

        if ((bus->sih->chip == BCM4350_CHIP_ID) ||
            BCM4345_CHIP(bus->sih->chip) ||
            (bus->sih->chip == BCM4354_CHIP_ID) ||
            (bus->sih->chip == BCM4358_CHIP_ID) ||
            (bus->sih->chip == BCM43569_CHIP_ID) ||
            (bus->sih->chip == BCM4371_CHIP_ID)) {
            enabval &= CC_CHIPCTRL3_SR_ENG_ENABLE;
        }

        if (enabval) {
            cap = TRUE;
        }
    } else {
        data = bcmsdh_reg_read(
            bus->sdh,
            si_get_pmu_reg_addr(bus->sih, OFFSETOF(chipcregs_t, retention_ctl)),
            0x4);
        if ((data & (RCTL_MACPHY_DISABLE_MASK | RCTL_LOGIC_DISABLE_MASK)) ==
            0) {
            cap = TRUE;
        }
    }

    return cap;
}

static int dhdsdio_sr_init(dhd_bus_t *bus)
{
    uint8 val;
    int err = 0;

    if (bus->sih->chip == BCM43012_CHIP_ID) {
        val = bcmsdh_cfg_read(bus->sdh, SDIO_FUNC_1, SBSDIO_FUNC1_WAKEUPCTRL,
                              NULL);
        val |= 1 << SBSDIO_FUNC1_WCTRL_ALPWAIT_SHIFT;
        bcmsdh_cfg_write(bus->sdh, SDIO_FUNC_1, SBSDIO_FUNC1_WAKEUPCTRL,
                         1 << SBSDIO_FUNC1_WCTRL_ALPWAIT_SHIFT, &err);
        val = bcmsdh_cfg_read(bus->sdh, SDIO_FUNC_1, SBSDIO_FUNC1_WAKEUPCTRL,
                              NULL);
    } else {
        val = bcmsdh_cfg_read(bus->sdh, SDIO_FUNC_1, SBSDIO_FUNC1_WAKEUPCTRL,
                              NULL);
        val |= 1 << SBSDIO_FUNC1_WCTRL_HTWAIT_SHIFT;
        bcmsdh_cfg_write(bus->sdh, SDIO_FUNC_1, SBSDIO_FUNC1_WAKEUPCTRL,
                         1 << SBSDIO_FUNC1_WCTRL_HTWAIT_SHIFT, &err);
        val = bcmsdh_cfg_read(bus->sdh, SDIO_FUNC_1, SBSDIO_FUNC1_WAKEUPCTRL,
                              NULL);
    }

#ifdef USE_CMD14
    /* Add CMD14 Support */
    dhdsdio_devcap_set(bus, (SDIOD_CCCR_BRCM_CARDCAP_CMD14_SUPPORT |
                             SDIOD_CCCR_BRCM_CARDCAP_CMD14_EXT));
#endif /* USE_CMD14 */

    if (CHIPID(bus->sih->chip) == BCM43430_CHIP_ID ||
        CHIPID(bus->sih->chip) == BCM43018_CHIP_ID ||
        CHIPID(bus->sih->chip) == BCM4339_CHIP_ID ||
        CHIPID(bus->sih->chip) == BCM43012_CHIP_ID ||
        CHIPID(bus->sih->chip) == BCM4362_CHIP_ID ||
        CHIPID(bus->sih->chip) == BCM43014_CHIP_ID ||
        CHIPID(bus->sih->chip) == BCM43751_CHIP_ID ||
        CHIPID(bus->sih->chip) == BCM43752_CHIP_ID) {
        dhdsdio_devcap_set(bus, SDIOD_CCCR_BRCM_CARDCAP_CMD_NODEC);
    }

    if (bus->sih->chip == BCM43012_CHIP_ID) {
        bcmsdh_cfg_write(bus->sdh, SDIO_FUNC_1, SBSDIO_FUNC1_CHIPCLKCSR,
                         SBSDIO_HT_AVAIL_REQ, &err);
    } else {
        bcmsdh_cfg_write(bus->sdh, SDIO_FUNC_1, SBSDIO_FUNC1_CHIPCLKCSR,
                         SBSDIO_FORCE_HT, &err);
    }
    bus->_slpauto = dhd_slpauto ? TRUE : FALSE;

    bus->_srenab = TRUE;

    return 0;
}
#endif /* BCMSPI */

/*
 * FIX: Be sure KSO bit is enabled
 * Currently, it's defaulting to 0 which should be 1.
 */
static int dhdsdio_clk_kso_init(dhd_bus_t *bus)
{
    uint8 val;
    int err = 0;

    /* set flag */
    bus->kso = TRUE;

    /*
     * Enable KeepSdioOn (KSO) bit for normal operation
     * Default is 0 (4334A0) so set it. Fixed in B0.
     */
    val = bcmsdh_cfg_read(bus->sdh, SDIO_FUNC_1, SBSDIO_FUNC1_SLEEPCSR, NULL);
    if (!(val & SBSDIO_FUNC1_SLEEPCSR_KSO_MASK)) {
        val |=
            (SBSDIO_FUNC1_SLEEPCSR_KSO_EN << SBSDIO_FUNC1_SLEEPCSR_KSO_SHIFT);
        bcmsdh_cfg_write(bus->sdh, SDIO_FUNC_1, SBSDIO_FUNC1_SLEEPCSR, val,
                         &err);
        if (err) {
            DHD_ERROR(
                ("%s: SBSDIO_FUNC1_SLEEPCSR err: 0x%x\n", __FUNCTION__, err));
        }
    }

    return 0;
}

#define KSO_DBG(x)
#define KSO_WAIT_US 50
#define KSO_WAIT_MS 1
#define KSO_SLEEP_RETRY_COUNT 20
#define KSO_WAKE_RETRY_COUNT 100
#define ERROR_BCME_NODEVICE_MAX 1

#define DEFAULT_MAX_KSO_ATTEMPTS (PMU_MAX_TRANSITION_DLY / KSO_WAIT_US)
#ifndef CUSTOM_MAX_KSO_ATTEMPTS
#define CUSTOM_MAX_KSO_ATTEMPTS DEFAULT_MAX_KSO_ATTEMPTS
#endif // endif

static int dhdsdio_clk_kso_enab(dhd_bus_t *bus, bool on)
{
    uint8 wr_val = 0, rd_val, cmp_val, bmask;
    int err = 0;
    int try_cnt = 0, try_max = CUSTOM_MAX_KSO_ATTEMPTS;
    struct dhd_conf *conf = bus->dhd->conf;
#if (LINUX_VERSION_CODE >= KERNEL_VERSION(5, 1, 0))
    wifi_adapter_info_t *adapter = NULL;
    uint32 bus_type = -1, bus_num = -1, slot_num = -1;
#endif /* LINUX_VERSION_CODE >= KERNEL_VERSION(5, 1, 0) */

    KSO_DBG(("%s> op:%s\n", __FUNCTION__, (on ? "KSO_SET" : "KSO_CLR")));

#if (LINUX_VERSION_CODE >= KERNEL_VERSION(5, 1, 0))
    dhd_bus_get_ids(bus, &bus_type, &bus_num, &slot_num);
    adapter = dhd_wifi_platform_get_adapter(bus_type, bus_num, slot_num);
    sdio_retune_crc_disable(adapter->sdio_func);
    if (on) {
        sdio_retune_hold_now(adapter->sdio_func);
    }
#endif /* LINUX_VERSION_CODE >= KERNEL_VERSION(5, 1, 0) */

    wr_val |= (on << SBSDIO_FUNC1_SLEEPCSR_KSO_SHIFT);

    bcmsdh_cfg_write(bus->sdh, SDIO_FUNC_1, SBSDIO_FUNC1_SLEEPCSR, wr_val,
                     &err);

    /* In case of 43012 chip, the chip could go down immediately after KSO bit
     * is cleared. So the further reads of KSO register could fail. Thereby just
     * bailing out immediately after clearing KSO bit, to avoid polling of KSO
     * bit.
     */
    if ((!on) && (bus->sih->chip == BCM43012_CHIP_ID)) {
        goto exit;
    }

    if (on) {
        cmp_val =
            SBSDIO_FUNC1_SLEEPCSR_KSO_MASK | SBSDIO_FUNC1_SLEEPCSR_DEVON_MASK;
        bmask = cmp_val;

        OSL_SLEEP(0x3);
    } else {
        /* Put device to sleep, turn off  KSO  */
        cmp_val = 0;
        bmask = SBSDIO_FUNC1_SLEEPCSR_KSO_MASK;
    }

    if (conf->kso_try_max) {
        try_max = conf->kso_try_max;
    }
    do {
        rd_val =
            bcmsdh_cfg_read(bus->sdh, SDIO_FUNC_1, SBSDIO_FUNC1_SLEEPCSR, &err);
        if (((rd_val & bmask) == cmp_val) && !err) {
            break;
        }

        KSO_DBG(
            ("%s> KSO wr/rd retry:%d, ERR:%x \n", __FUNCTION__, try_cnt, err));

        if (((try_cnt + 1) % KSO_SLEEP_RETRY_COUNT) == 0) {
            OSL_SLEEP(KSO_WAIT_MS);
        } else {
            OSL_DELAY(KSO_WAIT_US);
        }

        bcmsdh_cfg_write(bus->sdh, SDIO_FUNC_1, SBSDIO_FUNC1_SLEEPCSR, wr_val,
                         &err);
    } while (try_cnt++ < try_max);

#ifdef KSO_DEBUG
    if (try_cnt > 0 && try_cnt <= 0xA) {
        conf->kso_try_array[0] += 1;
    } else if (try_cnt <= 0x32) {
        conf->kso_try_array[1] += 1;
    } else if (try_cnt <= 0x64) {
        conf->kso_try_array[0x2] += 1;
    } else if (try_cnt <= 0xC8) {
        conf->kso_try_array[0x3] += 1;
    } else if (try_cnt <= 0x1F4) {
        conf->kso_try_array[0x4] += 1;
    } else if (try_cnt <= 0x3E8) {
        conf->kso_try_array[0x5] += 1;
    } else if (try_cnt <= 0x7D0) {
        conf->kso_try_array[0x6] += 1;
    } else if (try_cnt <= 0x1388) {
        conf->kso_try_array[0x7] += 1;
    } else if (try_cnt <= 0x2710) {
        conf->kso_try_array[0x8] += 1;
    } else {
        conf->kso_try_array[0x9] += 1;
    }
#endif
    if (try_cnt > 0x2) {
        KSO_DBG(("%s> op:%s, try_cnt:%d, rd_val:%x, ERR:%x \n", __FUNCTION__,
                 (on ? "KSO_SET" : "KSO_CLR"), try_cnt, rd_val, err));
    }

    if (try_cnt > try_max) {
        DHD_ERROR(("%s> op:%s, ERROR: try_cnt:%d, rd_val:%x, ERR:%x \n",
                   __FUNCTION__, (on ? "KSO_SET" : "KSO_CLR"), try_cnt, rd_val,
                   err));
#ifdef KSO_DEBUG
        {
            int i;
            printk(KERN_CONT DHD_LOG_PREFIXS);
            for (i = 0; i < 0xA; i++) {
                printk(KERN_CONT "[%d]: %d, ", i, conf->kso_try_array[i]);
            }
            printk("\n");
        }
#endif
    }

exit:
#if (LINUX_VERSION_CODE >= KERNEL_VERSION(5, 1, 0))
    if (on) {
        sdio_retune_release(adapter->sdio_func);
    }
    sdio_retune_crc_enable(adapter->sdio_func);
#endif /* LINUX_VERSION_CODE >= KERNEL_VERSION(5, 1, 0) */

    return err;
}

static int dhdsdio_clk_kso_iovar(dhd_bus_t *bus, bool on)
{
    int err = 0;

    if (on == FALSE) {
        BUS_WAKE(bus);
        dhdsdio_clkctl(bus, CLK_AVAIL, FALSE);

        DHD_ERROR(("%s: KSO disable clk: 0x%x\n", __FUNCTION__,
                   bcmsdh_cfg_read(bus->sdh, SDIO_FUNC_1,
                                   SBSDIO_FUNC1_CHIPCLKCSR, &err)));
        dhdsdio_clk_kso_enab(bus, FALSE);
    } else {
        DHD_ERROR(("%s: KSO enable\n", __FUNCTION__));

        /* Make sure we have SD bus access */
        if (bus->clkstate == CLK_NONE) {
            DHD_ERROR(("%s: Request SD clk\n", __FUNCTION__));
            dhdsdio_clkctl(bus, CLK_SDONLY, FALSE);
        }

        dhdsdio_clk_kso_enab(bus, TRUE);

        DHD_ERROR(
            ("%s: sleepcsr: 0x%x\n", __FUNCTION__, dhdsdio_sleepcsr_get(bus)));
    }

    bus->kso = on;
    BCM_REFERENCE(err);

    return 0;
}

static uint8 dhdsdio_sleepcsr_get(dhd_bus_t *bus)
{
    int err = 0;
    uint8 val = 0;

    val = bcmsdh_cfg_read(bus->sdh, SDIO_FUNC_1, SBSDIO_FUNC1_SLEEPCSR, &err);
    if (err) {
        DHD_TRACE(("Failed to read SLEEPCSR: %d\n", err));
    }

    return val;
}

uint8 dhdsdio_devcap_get(dhd_bus_t *bus)
{
    return bcmsdh_cfg_read(bus->sdh, SDIO_FUNC_0, SDIOD_CCCR_BRCM_CARDCAP,
                           NULL);
}

static int dhdsdio_devcap_set(dhd_bus_t *bus, uint8 cap)
{
    int err = 0;

    bcmsdh_cfg_write(bus->sdh, SDIO_FUNC_0, SDIOD_CCCR_BRCM_CARDCAP, cap, &err);
    if (err) {
        DHD_ERROR(("%s: devcap set err: 0x%x\n", __FUNCTION__, err));
    }

    return 0;
}

static int dhdsdio_clk_devsleep_iovar(dhd_bus_t *bus, bool on)
{
    int err = 0, retry;
    uint8 val;

    retry = 0;
    if (on == TRUE) {
        /* Enter Sleep */

        /* Be sure we request clk before going to sleep
         * so we can wake-up with clk request already set
         * else device can go back to sleep immediately
         */
        if (!SLPAUTO_ENAB(bus)) {
            dhdsdio_clkctl(bus, CLK_AVAIL, FALSE);
        } else {
            val = bcmsdh_cfg_read(bus->sdh, SDIO_FUNC_1,
                                  SBSDIO_FUNC1_CHIPCLKCSR, &err);
            if ((val & SBSDIO_CSR_MASK) == 0) {
                DHD_ERROR(("%s: No clock before enter sleep:0x%x\n",
                           __FUNCTION__, val));

                /* Reset clock request */
                bcmsdh_cfg_write(bus->sdh, SDIO_FUNC_1, SBSDIO_FUNC1_CHIPCLKCSR,
                                 SBSDIO_ALP_AVAIL_REQ, &err);
                DHD_ERROR(("%s: clock before sleep:0x%x\n", __FUNCTION__,
                           bcmsdh_cfg_read(bus->sdh, SDIO_FUNC_1,
                                           SBSDIO_FUNC1_CHIPCLKCSR, &err)));
            }
        }

        DHD_TRACE(("%s: clk before sleep: 0x%x\n", __FUNCTION__,
                   bcmsdh_cfg_read(bus->sdh, SDIO_FUNC_1,
                                   SBSDIO_FUNC1_CHIPCLKCSR, &err)));
#ifdef USE_CMD14
        err = bcmsdh_sleep(bus->sdh, TRUE);
#else
        if ((SLPAUTO_ENAB(bus)) && (bus->idleclock == DHD_IDLE_STOP)) {
            if (sd1idle) {
                /* Change to SD1 mode */
                dhdsdio_set_sdmode(bus, 1);
            }
        }

        err = dhdsdio_clk_kso_enab(bus, FALSE);
        if (OOB_WAKEUP_ENAB(bus)) {
            err = bcmsdh_gpioout(bus->sdh, GPIO_DEV_WAKEUP,
                                 FALSE); /* GPIO_1 is off */
        }
#endif /* USE_CMD14 */

        if ((SLPAUTO_ENAB(bus)) && (bus->idleclock != DHD_IDLE_ACTIVE)) {
            DHD_TRACE(("%s: Turnoff SD clk\n", __FUNCTION__));
            /* Now remove the SD clock */
            err = dhdsdio_sdclk(bus, FALSE);
        }
    } else {
        /* Exit Sleep */
        /* Make sure we have SD bus access */
        if (bus->clkstate == CLK_NONE) {
            DHD_TRACE(("%s: Request SD clk\n", __FUNCTION__));
            dhdsdio_clkctl(bus, CLK_SDONLY, FALSE);
        }
#ifdef USE_CMD14
        err = bcmsdh_sleep(bus->sdh, FALSE);
        if (SLPAUTO_ENAB(bus) && (err != 0)) {
            OSL_DELAY(0x2710);
            DHD_TRACE(("%s: Resync device sleep\n", __FUNCTION__));

            /* Toggle sleep to resync with host and device */
            err = bcmsdh_sleep(bus->sdh, TRUE);
            OSL_DELAY(0x2710);
            err = bcmsdh_sleep(bus->sdh, FALSE);
            if (err) {
                OSL_DELAY(0x2710);
                DHD_ERROR(("%s: CMD14 exit failed again!\n", __FUNCTION__));

                /* Toggle sleep to resync with host and device */
                err = bcmsdh_sleep(bus->sdh, TRUE);
                OSL_DELAY(0x2710);
                err = bcmsdh_sleep(bus->sdh, FALSE);
                if (err) {
                    DHD_ERROR(("%s: CMD14 exit failed twice!\n", __FUNCTION__));
                    DHD_ERROR(
                        ("%s: FATAL: Device non-response!\n", __FUNCTION__));
                    err = 0;
                }
            }
        }
#else
        if (OOB_WAKEUP_ENAB(bus)) {
            err = bcmsdh_gpioout(bus->sdh, GPIO_DEV_WAKEUP,
                                 TRUE); /* GPIO_1 is on */
        }
        do {
            err = dhdsdio_clk_kso_enab(bus, TRUE);
            if (err) {
                OSL_SLEEP(0xA);
            }
        } while ((err != 0) && (++retry < 0x3));

        if (err != 0) {
            DHD_ERROR(("ERROR: kso set failed retry: %d\n", retry));
#ifndef BT_OVER_SDIO
            err = 0; /* continue anyway */
#endif /* BT_OVER_SDIO */
        }

        if ((SLPAUTO_ENAB(bus)) && (bus->idleclock == DHD_IDLE_STOP)) {
            dhdsdio_set_sdmode(bus, bus->sd_mode);
        }
#endif /* !USE_CMD14 */

        if (err == 0) {
            uint8 csr;

            /* Wait for device ready during transition to wake-up */
            SPINWAIT_SLEEP(sdioh_spinwait_sleep,
                           (((csr = dhdsdio_sleepcsr_get(bus)) &
                             SBSDIO_FUNC1_SLEEPCSR_DEVON_MASK) !=
                            (SBSDIO_FUNC1_SLEEPCSR_DEVON_MASK)),
                           (0x4E20));

            DHD_TRACE(("%s: ExitSleep sleepcsr: 0x%x\n", __FUNCTION__, csr));

            if (!(csr & SBSDIO_FUNC1_SLEEPCSR_DEVON_MASK)) {
                DHD_ERROR(("%s:ERROR: ExitSleep device NOT Ready! 0x%x\n",
                           __FUNCTION__, csr));
                err = BCME_NODEVICE;
            }

            SPINWAIT_SLEEP(
                sdioh_spinwait_sleep,
                (((csr = bcmsdh_cfg_read(bus->sdh, SDIO_FUNC_1,
                                         SBSDIO_FUNC1_CHIPCLKCSR, &err)) &
                  SBSDIO_HT_AVAIL) != (SBSDIO_HT_AVAIL)),
                (DHD_WAIT_HTAVAIL));

            DHD_TRACE(
                ("%s: SBSDIO_FUNC1_CHIPCLKCSR : 0x%x\n", __FUNCTION__, csr));
            if (!err && ((csr & SBSDIO_HT_AVAIL) != SBSDIO_HT_AVAIL)) {
                DHD_ERROR(
                    ("%s:ERROR: device NOT Ready! 0x%x\n", __FUNCTION__, csr));
                err = BCME_NODEVICE;
            }
        }
    }

    /* Update if successful */
    if (err == 0) {
        bus->kso = on ? FALSE : TRUE;
    } else {
        DHD_ERROR(("%s: Sleep request failed: kso:%d on:%d err:%d\n",
                   __FUNCTION__, bus->kso, on, err));
        if (!on && retry > 0x2) {
            bus->kso = FALSE;
        }
    }

    return err;
}

/* Turn backplane clock on or off */
static int dhdsdio_htclk(dhd_bus_t *bus, bool on, bool pendok)
{
#define HT_AVAIL_ERROR_MAX 0xA
    static int ht_avail_error = 0;
    int err;
    uint8 clkctl, clkreq, devctl;
    bcmsdh_info_t *sdh;

    DHD_TRACE(("%s: Enter\n", __FUNCTION__));

    clkctl = 0;
    sdh = bus->sdh;

    if (!KSO_ENAB(bus)) {
        return BCME_OK;
    }

    if (SLPAUTO_ENAB(bus)) {
        bus->clkstate = (on ? CLK_AVAIL : CLK_SDONLY);
        return BCME_OK;
    }

    if (on) {
        /* Request HT Avail */
        clkreq = bus->alp_only ? SBSDIO_ALP_AVAIL_REQ : SBSDIO_HT_AVAIL_REQ;

#ifdef BCMSPI
        dhdsdio_wkwlan(bus, TRUE);
#endif /* BCMSPI */

        bcmsdh_cfg_write(sdh, SDIO_FUNC_1, SBSDIO_FUNC1_CHIPCLKCSR, clkreq,
                         &err);
        if (err) {
            ht_avail_error++;
            if (ht_avail_error < HT_AVAIL_ERROR_MAX) {
                DHD_ERROR(
                    ("%s: HT Avail request error: %d\n", __FUNCTION__, err));
            } else if (ht_avail_error == HT_AVAIL_ERROR_MAX) {
                bus->dhd->hang_reason = HANG_REASON_HT_AVAIL_ERROR;
                dhd_os_send_hang_message(bus->dhd);
            }
            return BCME_ERROR;
        } else {
            ht_avail_error = 0;
        }

        /* Check current status */
        clkctl =
            bcmsdh_cfg_read(sdh, SDIO_FUNC_1, SBSDIO_FUNC1_CHIPCLKCSR, &err);
        if (err) {
            DHD_ERROR(("%s: HT Avail read error: %d\n", __FUNCTION__, err));
            return BCME_ERROR;
        }

#if !defined(OOB_INTR_ONLY)
        /* Go to pending and await interrupt if appropriate */
        if (!SBSDIO_CLKAV(clkctl, bus->alp_only) && pendok) {
            /* Allow only clock-available interrupt */
            devctl = bcmsdh_cfg_read(sdh, SDIO_FUNC_1, SBSDIO_DEVICE_CTL, &err);
            if (err) {
                DHD_ERROR(("%s: Devctl access error setting CA: %d\n",
                           __FUNCTION__, err));
                return BCME_ERROR;
            }

            devctl |= SBSDIO_DEVCTL_CA_INT_ONLY;
            bcmsdh_cfg_write(sdh, SDIO_FUNC_1, SBSDIO_DEVICE_CTL, devctl, &err);
            DHD_INFO(("CLKCTL: set PENDING\n"));
            bus->clkstate = CLK_PENDING;
            return BCME_OK;
        } else
#endif /* !defined (OOB_INTR_ONLY) */
        {
            if (bus->clkstate == CLK_PENDING) {
                /* Cancel CA-only interrupt filter */
                devctl =
                    bcmsdh_cfg_read(sdh, SDIO_FUNC_1, SBSDIO_DEVICE_CTL, &err);
                devctl &= ~SBSDIO_DEVCTL_CA_INT_ONLY;
                bcmsdh_cfg_write(sdh, SDIO_FUNC_1, SBSDIO_DEVICE_CTL, devctl,
                                 &err);
            }
        }
#ifndef BCMSDIOLITE
        /* Otherwise, wait here (polling) for HT Avail */
        if (!SBSDIO_CLKAV(clkctl, bus->alp_only)) {
            SPINWAIT_SLEEP(
                sdioh_spinwait_sleep,
                ((clkctl = bcmsdh_cfg_read(sdh, SDIO_FUNC_1,
                                           SBSDIO_FUNC1_CHIPCLKCSR, &err)),
                 !SBSDIO_CLKAV(clkctl, bus->alp_only)),
                PMU_MAX_TRANSITION_DLY);
        }
        if (err) {
            DHD_ERROR(("%s: HT Avail request error: %d\n", __FUNCTION__, err));
            return BCME_ERROR;
        }
        if (!SBSDIO_CLKAV(clkctl, bus->alp_only)) {
            DHD_ERROR(("%s: HT Avail timeout (%d): clkctl 0x%02x\n",
                       __FUNCTION__, PMU_MAX_TRANSITION_DLY, clkctl));
            return BCME_ERROR;
        }
#endif /* BCMSDIOLITE */
        /* Mark clock available */
        bus->clkstate = CLK_AVAIL;
        DHD_INFO(("CLKCTL: turned ON\n"));

#if defined(DHD_DEBUG)
        if (bus->alp_only == TRUE) {
#if !defined(BCMLXSDMMC)
            if (!SBSDIO_ALPONLY(clkctl)) {
                DHD_ERROR(("%s: HT Clock, when ALP Only\n", __FUNCTION__));
            }
#endif /* !defined(BCMLXSDMMC) */
        } else {
            if (SBSDIO_ALPONLY(clkctl)) {
                DHD_ERROR(("%s: HT Clock should be on.\n", __FUNCTION__));
            }
        }
#endif /* defined (DHD_DEBUG) */

        bus->activity = TRUE;
#ifdef DHD_USE_IDLECOUNT
        bus->idlecount = 0;
#endif /* DHD_USE_IDLECOUNT */
    } else {
        clkreq = 0;

        if (bus->clkstate == CLK_PENDING) {
            /* Cancel CA-only interrupt filter */
            devctl = bcmsdh_cfg_read(sdh, SDIO_FUNC_1, SBSDIO_DEVICE_CTL, &err);
            devctl &= ~SBSDIO_DEVCTL_CA_INT_ONLY;
            bcmsdh_cfg_write(sdh, SDIO_FUNC_1, SBSDIO_DEVICE_CTL, devctl, &err);
        }

        bus->clkstate = CLK_SDONLY;
        if (!SR_ENAB(bus)) {
            bcmsdh_cfg_write(sdh, SDIO_FUNC_1, SBSDIO_FUNC1_CHIPCLKCSR, clkreq,
                             &err);
            DHD_INFO(("CLKCTL: turned OFF\n"));
            if (err) {
                DHD_ERROR(("%s: Failed access turning clock off: %d\n",
                           __FUNCTION__, err));
                return BCME_ERROR;
            }
        }
#ifdef BCMSPI
        dhdsdio_wkwlan(bus, FALSE);
#endif /* BCMSPI */
    }
    return BCME_OK;
}

/* Change SD1/SD4 bus mode */
static int dhdsdio_set_sdmode(dhd_bus_t *bus, int32 sd_mode)
{
    int err;

    err = bcmsdh_iovar_op(bus->sdh, "sd_mode", NULL, 0, &sd_mode,
                          sizeof(sd_mode), TRUE);
    if (err) {
        DHD_ERROR(("%s: error changing sd_mode: %d\n", __FUNCTION__, err));
        return BCME_ERROR;
    }
    return BCME_OK;
}

/* Change idle/active SD state */
static int dhdsdio_sdclk(dhd_bus_t *bus, bool on)
{
#ifndef BCMSPI
    int err;
    int32 iovalue;

    DHD_TRACE(("%s: Enter\n", __FUNCTION__));

    if (on) {
        if (bus->idleclock == DHD_IDLE_STOP) {
            /* Turn on clock and restore mode */
            iovalue = 1;
            err = bcmsdh_iovar_op(bus->sdh, "sd_clock", NULL, 0, &iovalue,
                                  sizeof(iovalue), TRUE);
            if (err) {
                DHD_ERROR(
                    ("%s: error enabling sd_clock: %d\n", __FUNCTION__, err));
                return BCME_ERROR;
            }
        } else if (bus->idleclock != DHD_IDLE_ACTIVE) {
            /* Restore clock speed */
            iovalue = bus->sd_divisor;
            err = bcmsdh_iovar_op(bus->sdh, "sd_divisor", NULL, 0, &iovalue,
                                  sizeof(iovalue), TRUE);
            if (err) {
                DHD_ERROR(("%s: error restoring sd_divisor: %d\n", __FUNCTION__,
                           err));
                return BCME_ERROR;
            }
        }
        bus->clkstate = CLK_SDONLY;
    } else {
        /* Stop or slow the SD clock itself */
        if ((bus->sd_divisor == -1) || (bus->sd_mode == -1)) {
            DHD_TRACE(("%s: can't idle clock, divisor %d mode %d\n",
                       __FUNCTION__, bus->sd_divisor, bus->sd_mode));
            return BCME_ERROR;
        }
        if (bus->idleclock == DHD_IDLE_STOP) {
            iovalue = 0;
            err = bcmsdh_iovar_op(bus->sdh, "sd_clock", NULL, 0, &iovalue,
                                  sizeof(iovalue), TRUE);
            if (err) {
                DHD_ERROR(
                    ("%s: error disabling sd_clock: %d\n", __FUNCTION__, err));
                return BCME_ERROR;
            }
        } else if (bus->idleclock != DHD_IDLE_ACTIVE) {
            /* Set divisor to idle value */
            iovalue = bus->idleclock;
            err = bcmsdh_iovar_op(bus->sdh, "sd_divisor", NULL, 0, &iovalue,
                                  sizeof(iovalue), TRUE);
            if (err) {
                DHD_ERROR(
                    ("%s: error changing sd_divisor: %d\n", __FUNCTION__, err));
                return BCME_ERROR;
            }
        }
        bus->clkstate = CLK_NONE;
    }
#endif /* BCMSPI */

    return BCME_OK;
}

/* Transition SD and backplane clock readiness */
static int dhdsdio_clkctl(dhd_bus_t *bus, uint target, bool pendok)
{
    int ret = BCME_OK;
#ifdef DHD_DEBUG
    uint oldstate = bus->clkstate;
#endif /* DHD_DEBUG */

    DHD_TRACE(("%s: Enter\n", __FUNCTION__));

    /* Early exit if we're already there */
    if (bus->clkstate == target) {
        if (target == CLK_AVAIL) {
            dhd_os_wd_timer(bus->dhd, dhd_watchdog_ms);
            bus->activity = TRUE;
#ifdef DHD_USE_IDLECOUNT
            bus->idlecount = 0;
#endif /* DHD_USE_IDLECOUNT */
        }
        return ret;
    }

    switch (target) {
        case CLK_AVAIL:
            /* Make sure SD clock is available */
            if (bus->clkstate == CLK_NONE) {
                dhdsdio_sdclk(bus, TRUE);
            }
            /* Now request HT Avail on the backplane */
            ret = dhdsdio_htclk(bus, TRUE, pendok);
            if (ret == BCME_OK) {
                dhd_os_wd_timer(bus->dhd, dhd_watchdog_ms);
                bus->activity = TRUE;
#ifdef DHD_USE_IDLECOUNT
                bus->idlecount = 0;
#endif /* DHD_USE_IDLECOUNT */
            }
            break;

        case CLK_SDONLY:

#ifdef BT_OVER_SDIO
            /*
             * If the request is to switch off Back plane clock,
             * confirm that BT is inactive before doing so.
             * If this call had come from Non Watchdog context any way
             * the Watchdog would switch off the clock again when
             * nothing is to be done & Bt has finished using the bus.
             */
            if (bus->bt_use_count != 0) {
                DHD_INFO(("%s(): Req CLK_SDONLY, BT is active %d not switching "
                          "off \r\n",
                          __FUNCTION__, bus->bt_use_count));
                ret = BCME_OK;
                dhd_os_wd_timer(bus->dhd, dhd_watchdog_ms);
                break;
            }

            DHD_INFO(
                ("%s(): Request CLK_NONE BT is NOT active switching off \r\n",
                 __FUNCTION__));
#endif /* BT_OVER_SDIO */

            /* Remove HT request, or bring up SD clock */
            if (bus->clkstate == CLK_NONE) {
                ret = dhdsdio_sdclk(bus, TRUE);
            } else if (bus->clkstate == CLK_AVAIL) {
                ret = dhdsdio_htclk(bus, FALSE, FALSE);
            } else {
                DHD_ERROR(("dhdsdio_clkctl: request for %d -> %d\n",
                           bus->clkstate, target));
            }
            if (ret == BCME_OK) {
                dhd_os_wd_timer(bus->dhd, dhd_watchdog_ms);
            }
            break;

        case CLK_NONE:

#ifdef BT_OVER_SDIO
            /*
             * If the request is to switch off Back plane clock,
             * confirm that BT is inactive before doing so.
             * If this call had come from Non Watchdog context any way
             * the Watchdog would switch off the clock again when
             * nothing is to be done & Bt has finished using the bus.
             */
            if (bus->bt_use_count != 0) {
                DHD_INFO(("%s(): Request CLK_NONE BT is active %d not "
                          "switching off \r\n",
                          __FUNCTION__, bus->bt_use_count));
                ret = BCME_OK;
                break;
            }

            DHD_INFO(
                ("%s(): Request CLK_NONE BT is NOT active switching off \r\n",
                 __FUNCTION__));
#endif /* BT_OVER_SDIO */

            /* Make sure to remove HT request */
            if (bus->clkstate == CLK_AVAIL) {
                ret = dhdsdio_htclk(bus, FALSE, FALSE);
            }
            /* Now remove the SD clock */
            ret = dhdsdio_sdclk(bus, FALSE);
#ifdef DHD_DEBUG
            if (bus->dhd->dhd_console_ms == 0)
#endif /* DHD_DEBUG */
                if (bus->poll == 0) {
                    dhd_os_wd_timer(bus->dhd, 0);
                }
            break;
    }
#ifdef DHD_DEBUG
    DHD_INFO(("dhdsdio_clkctl: %d -> %d\n", oldstate, bus->clkstate));
#endif /* DHD_DEBUG */

    return ret;
}

static int dhdsdio_bussleep(dhd_bus_t *bus, bool sleep)
{
    int err = 0;
    bcmsdh_info_t *sdh = bus->sdh;
    sdpcmd_regs_t *regs = bus->regs;
    uint retries = 0;
#if defined(BCMSDIOH_STD)
    uint32 sd3_tuning_disable = FALSE;
#endif /* BCMSDIOH_STD */

    DHD_INFO(("dhdsdio_bussleep: request %s (currently %s)\n",
              (sleep ? "SLEEP" : "WAKE"), (bus->sleeping ? "SLEEP" : "WAKE")));

    if (bus->dhd->hang_was_sent) {
        return BCME_ERROR;
    }

    /* Done if we're already in the requested state */
    if (sleep == bus->sleeping) {
        return BCME_OK;
    }

    /* Going to sleep: set the alarm and turn off the lights... */
    if (sleep) {
        /* Don't sleep if something is pending */
#ifdef DHD_USE_IDLECOUNT
        if (bus->dpc_sched || bus->rxskip || pktq_n_pkts_tot(&bus->txq) ||
            bus->readframes || bus->ctrl_frame_stat)
#else
        if (bus->dpc_sched || bus->rxskip || pktq_n_pkts_tot(&bus->txq))
#endif /* DHD_USE_IDLECOUNT */
            return BCME_BUSY;

#ifdef BT_OVER_SDIO
        /*
         * The following is the assumption based on which the hook is placed.
         * From WLAN driver, either from the active contexts OR from the
         * Watchdog contexts we will be attempting to Go to Sleep. AT that
         * moment if we see that BT is still actively using the bus, we will
         * return BCME_BUSY from here, but the bus->sleeping state would not
         * have changed. So the caller can then schedule the Watchdog again
         * which will come and attempt to sleep at a later point.
         *
         * In case if BT is the only one and is the last user, we don't switch
         * off the clock immediately, we allow the WLAN to decide when to sleep
         * i.e from the watchdog. Now if the watchdog becomes active and
         * attempts to switch off the clock and if another WLAN context is
         * active they are any way serialized with sdlock.
         */
        if (bus->bt_use_count != 0) {
            DHD_INFO(("%s(): Cannot sleep BT is active \r\n", __FUNCTION__));
            return BCME_BUSY;
        }
#endif /* !BT_OVER_SDIO */

        if (!SLPAUTO_ENAB(bus)) {
            /* Disable SDIO interrupts (no longer interested) */
            bcmsdh_intr_disable(bus->sdh);

            /* Make sure the controller has the bus up */
            dhdsdio_clkctl(bus, CLK_AVAIL, FALSE);

            /* Tell device to start using OOB wakeup */
            W_SDREG(SMB_USE_OOB, &regs->tosbmailbox, retries);
            if (retries > retry_limit) {
                DHD_ERROR(("CANNOT SIGNAL CHIP, WILL NOT WAKE UP!!\n"));
            }

            /* Turn off our contribution to the HT clock request */
            dhdsdio_clkctl(bus, CLK_SDONLY, FALSE);

            bcmsdh_cfg_write(sdh, SDIO_FUNC_1, SBSDIO_FUNC1_CHIPCLKCSR,
                             SBSDIO_FORCE_HW_CLKREQ_OFF, NULL);

            /* Isolate the bus */
            bcmsdh_cfg_write(sdh, SDIO_FUNC_1, SBSDIO_DEVICE_CTL,
                             SBSDIO_DEVCTL_PADS_ISO, NULL);
        } else {
#ifdef FORCE_SWOOB_ENABLE
            /* Tell device to start using OOB wakeup */
            W_SDREG(SMB_USE_OOB, &regs->tosbmailbox, retries);
            if (retries > retry_limit) {
                DHD_ERROR(("CANNOT SIGNAL CHIP, WILL NOT WAKE UP!!\n"));
            }
#endif
            /* Leave interrupts enabled since device can exit sleep and
             * interrupt host
             */
            err = dhdsdio_clk_devsleep_iovar(bus, TRUE /* sleep */);
        }

        /* Change state */
        bus->sleeping = TRUE;
#if defined(BCMSDIOH_STD)
        sd3_tuning_disable = TRUE;
        err = bcmsdh_iovar_op(bus->sdh, "sd3_tuning_disable", NULL, 0,
                              &sd3_tuning_disable, sizeof(sd3_tuning_disable),
                              TRUE);
#endif /* BCMSDIOH_STD */
#if defined(SUPPORT_P2P_GO_PS)
        wake_up(&bus->bus_sleep);
#endif /* LINUX && SUPPORT_P2P_GO_PS */
    } else {
        /* Waking up: bus power up is ok, set local state */

        if (!SLPAUTO_ENAB(bus)) {
            bcmsdh_cfg_write(sdh, SDIO_FUNC_1, SBSDIO_FUNC1_CHIPCLKCSR, 0,
                             &err);

            /* Force pad isolation off if possible (in case power never toggled)
             */
            bcmsdh_cfg_write(sdh, SDIO_FUNC_1, SBSDIO_DEVICE_CTL, 0, NULL);

            /* Make sure the controller has the bus up */
            dhdsdio_clkctl(bus, CLK_AVAIL, FALSE);

            /* Send misc interrupt to indicate OOB not needed */
            W_SDREG(0, &regs->tosbmailboxdata, retries);
            if (retries <= retry_limit) {
                W_SDREG(SMB_DEV_INT, &regs->tosbmailbox, retries);
            }

            if (retries > retry_limit) {
                DHD_ERROR(("CANNOT SIGNAL CHIP TO CLEAR OOB!!\n"));
            }

            /* Make sure we have SD bus access */
            dhdsdio_clkctl(bus, CLK_SDONLY, FALSE);

            /* Enable interrupts again */
            if (bus->intr && (bus->dhd->busstate == DHD_BUS_DATA)) {
                bus->intdis = FALSE;
                bcmsdh_intr_enable(bus->sdh);
            }
        } else {
            err = dhdsdio_clk_devsleep_iovar(bus, FALSE /* wake */);
#ifdef FORCE_SWOOB_ENABLE
            /* Send misc interrupt to indicate OOB not needed */
            W_SDREG(0, &regs->tosbmailboxdata, retries);
            if (retries <= retry_limit) {
                W_SDREG(SMB_DEV_INT, &regs->tosbmailbox, retries);
            }
#endif
#ifdef BT_OVER_SDIO
            if (err < 0) {
                struct net_device *net = NULL;
                dhd_pub_t *dhd = bus->dhd;
                net = dhd_idx2net(dhd, 0);
                if (net != NULL) {
                    DHD_ERROR(("<< WIFI HANG by KSO Enabled failure\n"));
                    dhd_os_sdunlock(dhd);
                    net_os_send_hang_message(net);
                    dhd_os_sdlock(dhd);
                } else {
                    DHD_ERROR(("<< WIFI HANG Fail because net is NULL\n"));
                }
            }
#endif /* BT_OVER_SDIO */
        }

        if (err == 0) {
            /* Change state */
            bus->sleeping = FALSE;
#if defined(BCMSDIOH_STD)
            sd3_tuning_disable = FALSE;
            err = bcmsdh_iovar_op(bus->sdh, "sd3_tuning_disable", NULL, 0,
                                  &sd3_tuning_disable,
                                  sizeof(sd3_tuning_disable), TRUE);
#endif /* BCMSDIOH_STD */
        }
    }

    return err;
}

#ifdef BT_OVER_SDIO
/*
 * Call this function to Get the Clock running.
 * Assumes that the caller holds the sdlock.
 * bus - Pointer to the dhd_bus handle
 * can_wait - TRUE if the caller can wait until the clock becomes ready
 *            FALSE if the caller cannot wait
 */
int __dhdsdio_clk_enable(struct dhd_bus *bus, bus_owner_t owner, int can_wait)
{
    int ret = BCME_ERROR;

    BCM_REFERENCE(owner);

    bus->bt_use_count++;

    /*
     * We can call BUS_WAKE, clkctl multiple times, both of the items
     * have states and if its already ON, no new configuration is done
     */

    /* Wake up the Dongle FW from SR */
    BUS_WAKE(bus);

    /*
     * Make sure back plane ht clk is on
     * CLK_AVAIL - Turn On both SD & HT clock
     */
    ret = dhdsdio_clkctl(bus, CLK_AVAIL, can_wait);

    DHD_INFO(("%s():bt_use_count %d \r\n", __FUNCTION__, bus->bt_use_count));
    return ret;
}

/*
 * Call this function to relinquish the Clock.
 * Assumes that the caller holds the sdlock.
 * bus - Pointer to the dhd_bus handle
 * can_wait - TRUE if the caller can wait until the clock becomes ready
 *            FALSE if the caller cannot wait
 */
int __dhdsdio_clk_disable(struct dhd_bus *bus, bus_owner_t owner, int can_wait)
{
    int ret = BCME_ERROR;

    BCM_REFERENCE(owner);
    BCM_REFERENCE(can_wait);

    if (bus->bt_use_count == 0) {
        DHD_ERROR(("%s(): Clocks are already turned off \r\n", __FUNCTION__));
        return ret;
    }

    bus->bt_use_count--;

    /*
     * When the SDIO Bus is shared between BT & WLAN, we turn Off the clock
     * once the last user has relinqushed the same. But there are two schemes
     * in that too. We consider WLAN as the  bus master (even if its not
     * active). Even when the WLAN is OFF the DHD Watchdog is active.
     * So this Bus Watchdog is the context whill put the Bus to sleep.
     * Refer dhd_bus_watchdog function
     */

    ret = BCME_OK;
    DHD_INFO(("%s():bt_use_count %d \r\n", __FUNCTION__, bus->bt_use_count));
    return ret;
}

void dhdsdio_reset_bt_use_count(struct dhd_bus *bus)
{
    /* reset bt use count */
    bus->bt_use_count = 0;
}
#endif /* BT_OVER_SDIO */

#ifdef USE_DYNAMIC_F2_BLKSIZE
int dhdsdio_func_blocksize(dhd_pub_t *dhd, int function_num, int block_size)
{
    int func_blk_size = function_num;
    int bcmerr = 0;
    int result;

    bcmerr = dhd_bus_iovar_op(dhd, "sd_blocksize", &func_blk_size, sizeof(int),
                              &result, sizeof(int), IOV_GET);
    if (bcmerr != BCME_OK) {
        DHD_ERROR(
            ("%s: Get F%d Block size error\n", __FUNCTION__, function_num));
        return BCME_ERROR;
    }

    if (result != block_size) {
        DHD_ERROR(("%s: F%d Block size set from %d to %d\n", __FUNCTION__,
                   function_num, result, block_size));
        func_blk_size = function_num << 0x10 | block_size;
        bcmerr = dhd_bus_iovar_op(dhd, "sd_blocksize", NULL, 0, &func_blk_size,
                                  sizeof(int32), IOV_SET);
        if (bcmerr != BCME_OK) {
            DHD_ERROR(
                ("%s: Set F%d Block size error\n", __FUNCTION__, function_num));
            return BCME_ERROR;
        }
    }

    return BCME_OK;
}
#endif /* USE_DYNAMIC_F2_BLKSIZE */

#if defined(OOB_INTR_ONLY) || defined(FORCE_WOWLAN)
void dhd_enable_oob_intr(struct dhd_bus *bus, bool enable)
{
#if defined(HW_OOB) || defined(FORCE_WOWLAN)
    bcmsdh_enable_hw_oob_intr(bus->sdh, enable);
#else
    sdpcmd_regs_t *regs = bus->regs;
    uint retries = 0;

    dhdsdio_clkctl(bus, CLK_AVAIL, FALSE);
    if (enable == TRUE) {
        /* Tell device to start using OOB wakeup */
        W_SDREG(SMB_USE_OOB, &regs->tosbmailbox, retries);
        if (retries > retry_limit) {
            DHD_ERROR(("CANNOT SIGNAL CHIP, WILL NOT WAKE UP!!\n"));
        }
    } else {
        /* Send misc interrupt to indicate OOB not needed */
        W_SDREG(0, &regs->tosbmailboxdata, retries);
        if (retries <= retry_limit) {
            W_SDREG(SMB_DEV_INT, &regs->tosbmailbox, retries);
        }
    }

    /* Turn off our contribution to the HT clock request */
    dhdsdio_clkctl(bus, CLK_SDONLY, FALSE);
#endif /* !defined(HW_OOB) */
}
#endif /* defined(OOB_INTR_ONLY) */

int dhd_bus_txdata(struct dhd_bus *bus, void *pkt)
{
    int ret = BCME_ERROR;
    osl_t *osh;
    uint datalen, prec;

    DHD_TRACE(("%s: Enter\n", __FUNCTION__));

    osh = bus->dhd->osh;
    datalen = PKTLEN(osh, pkt);

#ifdef SDTEST
    /* Push the test header if doing loopback */
    if (bus->ext_loop) {
        uint8 *data;
        PKTPUSH(osh, pkt, SDPCM_TEST_HDRLEN);
        data = PKTDATA(osh, pkt);
        *data++ = SDPCM_TEST_ECHOREQ;
        *data++ = (uint8)bus->loopid++;
        *data++ = (datalen >> 0);
        *data++ = (datalen >> 0x8);
        datalen += SDPCM_TEST_HDRLEN;
    }
#else  /* SDTEST */
    BCM_REFERENCE(datalen);
#endif /* SDTEST */

#ifdef DHD_ULP
    dhd_ulp_set_path(bus->dhd, DHD_ULP_TX_DATA);
#endif /* DHD_ULP */

    prec = PRIO2PREC((PKTPRIO(pkt) & PRIOMASK));

    /* move from dhdsdio_sendfromq(), try to orphan skb early */
    if (bus->dhd->conf->orphan_move == 1) {
        PKTORPHAN(pkt, bus->dhd->conf->tsq);
    }

    /* Check for existing queue, current flow-control, pending event, or pending
     * clock */
    if (dhd_deferred_tx || bus->fcstate || pktq_n_pkts_tot(&bus->txq) ||
        bus->dpc_sched || (!DATAOK(bus)) ||
        (bus->flowcontrol & NBITVAL(prec)) || (bus->clkstate != CLK_AVAIL)) {
        bool deq_ret;
        int pkq_len = 0;

        DHD_TRACE(("%s: deferring pktq len %d\n", __FUNCTION__,
                   pktq_n_pkts_tot(&bus->txq)));
        bus->fcqueued++;

        /* Priority based enq */
        dhd_os_sdlock_txq(bus->dhd);
        deq_ret = dhd_prec_enq(bus->dhd, &bus->txq, pkt, prec);
        dhd_os_sdunlock_txq(bus->dhd);

        if (!deq_ret) {
#ifdef PROP_TXSTATUS
            if (DHD_PKTTAG_WLFCPKT(PKTTAG(pkt)) == 0)
#endif /* PROP_TXSTATUS */
            {
#ifdef DHDTCPACK_SUPPRESS
                if (dhd_tcpack_check_xmit(bus->dhd, pkt) == BCME_ERROR) {
                    DHD_ERROR(("%s %d: tcpack_suppress ERROR!!! Stop using\n",
                               __FUNCTION__, __LINE__));
                    dhd_tcpack_suppress_set(bus->dhd, TCPACK_SUP_OFF);
                }
#endif /* DHDTCPACK_SUPPRESS */
                dhd_txcomplete(bus->dhd, pkt, FALSE);
                PKTFREE(osh, pkt, TRUE);
            }
            ret = BCME_NORESOURCE;
        } else {
            ret = BCME_OK;
        }

        if (dhd_doflow) {
            dhd_os_sdlock_txq(bus->dhd);
            pkq_len = pktq_n_pkts_tot(&bus->txq);
            dhd_os_sdunlock_txq(bus->dhd);
        }
        if (dhd_doflow && pkq_len >= FCHI) {
            bool wlfc_enabled = FALSE;
#ifdef PROP_TXSTATUS
            wlfc_enabled =
                (dhd_wlfc_flowcontrol(bus->dhd, ON, FALSE) != WLFC_UNSUPPORTED);
#endif // endif
            if (!wlfc_enabled && dhd_doflow) {
                dhd_txflowcontrol(bus->dhd, ALL_INTERFACES, ON);
            }
        }

#ifdef DHD_DEBUG
        dhd_os_sdlock_txq(bus->dhd);
        if (pktqprec_n_pkts(&bus->txq, prec) > qcount[prec]) {
            qcount[prec] = pktqprec_n_pkts(&bus->txq, prec);
        }
        dhd_os_sdunlock_txq(bus->dhd);
#endif // endif

        /* Schedule DPC if needed to send queued packet(s) */
        if (dhd_deferred_tx && !bus->dpc_sched) {
            if (bus->dhd->conf->deferred_tx_len) {
                if (dhd_os_wd_timer_enabled(bus->dhd) == FALSE) {
                    bus->dpc_sched = TRUE;
                    dhd_sched_dpc(bus->dhd);
                }
                if (pktq_n_pkts_tot(&bus->txq) >=
                        bus->dhd->conf->deferred_tx_len &&
                    dhd_os_wd_timer_enabled(bus->dhd) == FALSE) {
                    bus->dpc_sched = TRUE;
                    dhd_sched_dpc(bus->dhd);
                }
            } else {
                bus->dpc_sched = TRUE;
                dhd_sched_dpc(bus->dhd);
            }
        }
    } else {
        int chan = SDPCM_DATA_CHANNEL;

#ifdef SDTEST
        chan = (bus->ext_loop ? SDPCM_TEST_CHANNEL : SDPCM_DATA_CHANNEL);
#endif // endif
        /* Lock: we're about to use shared data/code (and SDIO) */
        dhd_os_sdlock(bus->dhd);

        /* Otherwise, send it now */
        BUS_WAKE(bus);
        /* Make sure back plane ht clk is on, no pending allowed */
        dhdsdio_clkctl(bus, CLK_AVAIL, TRUE);

        ret = dhdsdio_txpkt(bus, chan, &pkt, 1, TRUE);
        if (ret != BCME_OK) {
            bus->dhd->tx_errors++;
        } else {
            bus->dhd->dstats.tx_bytes += datalen;
        }
        if ((bus->idletime == DHD_IDLE_IMMEDIATE) && !bus->dpc_sched &&
            NO_OTHER_ACTIVE_BUS_USER(bus)) {
            bus->activity = FALSE;
            dhdsdio_bussleep(bus, TRUE);
            dhdsdio_clkctl(bus, CLK_NONE, FALSE);
        }

        dhd_os_sdunlock(bus->dhd);
    }

    return ret;
}

/* align packet data pointer and packet length to n-byte boundary, process
 * packet headers, a new packet may be allocated if there is not enough head
 * and/or tail from for padding. the caller is responsible for updating the glom
 * size in the head packet (when glom is used)
 *
 * pad_pkt_len: returns the length of extra padding needed from the padding
 * packet, this parameter is taken in tx glom mode only
 *
 * new_pkt: out, pointer of the new packet allocated due to insufficient head
 * room for alignment padding, NULL if not needed, the caller is responsible for
 * freeing the new packet
 *
 * return: positive value - length of the packet, including head and tail
 * padding negative value - errors
 */
static int dhdsdio_txpkt_preprocess(dhd_bus_t *bus, void *pkt, int chan,
                                    int txseq, int prev_chain_total_len,
                                    bool last_chained_pkt, int *pad_pkt_len,
                                    void **new_pkt
#if defined(BCMSDIOH_TXGLOM_EXT)
                                    ,
                                    int first_frame
#endif
)
{
    osl_t *osh;
    uint8 *frame;
    int pkt_len;
    int modulo;
    int head_padding;
    int tail_padding = 0;
    uint32 swheader;
    uint32 swhdr_offset;
    bool alloc_new_pkt = FALSE;
    uint8 sdpcm_hdrlen =
        bus->txglom_enable ? SDPCM_HDRLEN_TXGLOM : SDPCM_HDRLEN;
#ifdef PKT_STATICS
    uint16 len;
#endif

    *new_pkt = NULL;
    osh = bus->dhd->osh;

#ifdef DHDTCPACK_SUPPRESS
    if (dhd_tcpack_check_xmit(bus->dhd, pkt) == BCME_ERROR) {
        DHD_ERROR(("%s %d: tcpack_suppress ERROR!!! Stop using it\n",
                   __FUNCTION__, __LINE__));
        dhd_tcpack_suppress_set(bus->dhd, TCPACK_SUP_OFF);
    }
#endif /* DHDTCPACK_SUPPRESS */

    /* Add space for the SDPCM hardware/software headers */
    PKTPUSH(osh, pkt, sdpcm_hdrlen);
    ASSERT(ISALIGNED((uintptr)PKTDATA(osh, pkt), 0x2));

    frame = (uint8 *)PKTDATA(osh, pkt);
    pkt_len = (uint16)PKTLEN(osh, pkt);

#ifdef PKT_STATICS
    len = (uint16)PKTLEN(osh, pkt);
    switch (chan) {
        case SDPCM_CONTROL_CHANNEL:
            bus->tx_statics.ctrl_count++;
            bus->tx_statics.ctrl_size += len;
            break;
        case SDPCM_DATA_CHANNEL:
            bus->tx_statics.data_count++;
            bus->tx_statics.data_size += len;
            break;
        case SDPCM_GLOM_CHANNEL:
            bus->tx_statics.glom_count++;
            bus->tx_statics.glom_size += len;
            break;
        case SDPCM_EVENT_CHANNEL:
            bus->tx_statics.event_count++;
            bus->tx_statics.event_size += len;
            break;
        case SDPCM_TEST_CHANNEL:
            bus->tx_statics.test_count++;
            bus->tx_statics.test_size += len;
            break;

        default:
            break;
    }
#endif /* PKT_STATICS */
#ifdef DHD_DEBUG
    if (PKTPRIO(pkt) < ARRAYSIZE(tx_packets)) {
        tx_packets[PKTPRIO(pkt)]++;
    }
#endif /* DHD_DEBUG */

    /* align the data pointer, allocate a new packet if there is not enough
     * space (new packet data pointer will be aligned thus no padding will be
     * needed)
     */
    head_padding = (uintptr)frame % DHD_SDALIGN;
    if (PKTHEADROOM(osh, pkt) < head_padding) {
        head_padding = 0;
        alloc_new_pkt = TRUE;
    } else {
        uint cur_chain_total_len;
        int chain_tail_padding = 0;

        /* All packets need to be aligned by DHD_SDALIGN */
        modulo = (pkt_len + head_padding) % DHD_SDALIGN;
        tail_padding = modulo > 0 ? (DHD_SDALIGN - modulo) : 0;

        /* Total pkt chain length needs to be aligned by block size,
         * unless it is a single pkt chain with total length less than one block
         * size, which we prefer sending by byte mode.
         *
         * Do the chain alignment here if
         * 1. This is the last pkt of the chain of multiple pkts or a single
         * pkt. 2-1. This chain is of multiple pkts, or 2-2. This is a single
         * pkt whose size is longer than one block size.
         */
        cur_chain_total_len =
            prev_chain_total_len + (head_padding + pkt_len + tail_padding);
        if (last_chained_pkt && bus->blocksize != 0 &&
            (cur_chain_total_len > (int)bus->blocksize ||
             prev_chain_total_len > 0)) {
            modulo = cur_chain_total_len % bus->blocksize;
            chain_tail_padding = modulo > 0 ? (bus->blocksize - modulo) : 0;
        }

#ifdef DHDENABLE_TAILPAD
        if (PKTTAILROOM(osh, pkt) < tail_padding) {
            /* We don't have tail room to align by DHD_SDALIGN */
            alloc_new_pkt = TRUE;
            bus->tx_tailpad_pktget++;
        } else if (PKTTAILROOM(osh, pkt) < tail_padding + chain_tail_padding) {
            /* We have tail room for tail_padding of this pkt itself, but not
             * for total pkt chain alignment by block size. Use the padding
             * packet to avoid memory copy if applicable, otherwise, just
             * allocate a new pkt.
             */
            if (bus->pad_pkt) {
                *pad_pkt_len = chain_tail_padding;
                bus->tx_tailpad_chain++;
            } else {
                alloc_new_pkt = TRUE;
                bus->tx_tailpad_pktget++;
            }
        } else
        /* This last pkt's tailroom is sufficient to hold both tail_padding
         * of the pkt itself and chain_tail_padding of total pkt chain
         */
#endif /* DHDENABLE_TAILPAD */
            tail_padding += chain_tail_padding;
    }

    DHD_INFO(
        ("%s sdhdr len + orig_pkt_len %d h_pad %d t_pad %d pad_pkt_len %d\n",
         __FUNCTION__, pkt_len, head_padding, tail_padding, *pad_pkt_len));

    if (alloc_new_pkt) {
        void *tmp_pkt;
        int newpkt_size;
        int cur_total_len;

        ASSERT(*pad_pkt_len == 0);

        DHD_INFO(("%s allocating new packet for padding\n", __FUNCTION__));

        /* head pointer is aligned now, no padding needed */
        head_padding = 0;

        /* update the tail padding as it depends on the head padding, since a
         * new packet is allocated, the head padding is non longer needed and
         * packet length is chagned
         */

        cur_total_len = prev_chain_total_len + pkt_len;
        if (last_chained_pkt && bus->blocksize != 0 &&
            (cur_total_len > (int)bus->blocksize || prev_chain_total_len > 0)) {
            modulo = cur_total_len % bus->blocksize;
            tail_padding = modulo > 0 ? (bus->blocksize - modulo) : 0;
        } else {
            modulo = pkt_len % DHD_SDALIGN;
            tail_padding = modulo > 0 ? (DHD_SDALIGN - modulo) : 0;
        }

        newpkt_size = PKTLEN(osh, pkt) + bus->blocksize + DHD_SDALIGN;
        bus->dhd->tx_realloc++;
        tmp_pkt = PKTGET(osh, newpkt_size, TRUE);
        if (tmp_pkt == NULL) {
            DHD_ERROR(("failed to alloc new %d byte packet\n", newpkt_size));
            return BCME_NOMEM;
        }
        PKTALIGN(osh, tmp_pkt, PKTLEN(osh, pkt), DHD_SDALIGN);
        bcopy(PKTDATA(osh, pkt), PKTDATA(osh, tmp_pkt), PKTLEN(osh, pkt));
        *new_pkt = tmp_pkt;
        pkt = tmp_pkt;
    }

    if (head_padding) {
        PKTPUSH(osh, pkt, head_padding);
    }

    frame = (uint8 *)PKTDATA(osh, pkt);
    bzero(frame, head_padding + sdpcm_hdrlen);
    pkt_len = (uint16)PKTLEN(osh, pkt);

    /* the header has the followming format
     * 4-byte HW frame tag: length, ~length (for glom this is the total length)
     *
     * 8-byte HW extesion flags (glom mode only) as the following:
     *			2-byte packet length, excluding HW tag and padding
     *			2-byte frame channel and frame flags (e.g. next frame following)
     *			2-byte header length
     *			2-byte tail padding size
     *
     * 8-byte SW frame tags as the following
     *			4-byte flags: host tx seq, channel, data offset
     */

    swhdr_offset = SDPCM_FRAMETAG_LEN;

    /* hardware frame tag:
     *
     * in tx-glom mode, dongle only checks the hardware frame tag in the first
     * packet and sees it as the total lenght of the glom (including tail
     * padding), for each packet in the glom, the packet length needs to be
     * updated, (see below PKTSETLEN)
     *
     * in non tx-glom mode, PKTLEN still need to include tail padding as to be
     * referred to in sdioh_request_buffer(). The tail length will be excluded
     * in dhdsdio_txpkt_postprocess().
     */
#if defined(BCMSDIOH_TXGLOM_EXT)
    if (bus->dhd->conf->txglom_bucket_size) {
        tail_padding = 0;
    }
#endif
    *(uint16 *)frame = (uint16)htol16(pkt_len);
    *(((uint16 *)frame) + 1) = (uint16)htol16(~pkt_len);
    pkt_len += tail_padding;

    /* hardware extesion flags */
    if (bus->txglom_enable) {
        uint32 hwheader1;
        uint32 hwheader2;
#ifdef BCMSDIOH_TXGLOM_EXT
        uint32 act_len = pkt_len - tail_padding;
        uint32 real_pad = 0;
        if (bus->dhd->conf->txglom_ext && !last_chained_pkt) {
            tail_padding = 0;
            if (first_frame == 0) {
                // first pkt, add pad to bucket size - recv offset
                pkt_len =
                    bus->dhd->conf->txglom_bucket_size - TXGLOM_RECV_OFFSET;
            } else {
                // add pad to bucket size
                pkt_len = bus->dhd->conf->txglom_bucket_size;
            }
            swhdr_offset += SDPCM_HWEXT_LEN;
            hwheader1 =
                (act_len - SDPCM_FRAMETAG_LEN) | (last_chained_pkt << 0x18);
            hwheader2 = (pkt_len - act_len) << 0x10;
            htol32_ua_store(hwheader1, frame + SDPCM_FRAMETAG_LEN);
            htol32_ua_store(hwheader2, frame + SDPCM_FRAMETAG_LEN + 0x4);
            real_pad = pkt_len - act_len;

            if (PKTTAILROOM(osh, pkt) < real_pad) {
                DHD_INFO(("%s : insufficient tailroom %d for %d real_pad\n",
                          __func__, (int)PKTTAILROOM(osh, pkt), real_pad));
                if (PKTPADTAILROOM(osh, pkt, real_pad)) {
                    DHD_ERROR(("CHK1: padding error size %d\n", real_pad));
                } else {
                    frame = (uint8 *)PKTDATA(osh, pkt);
                }
            }
        } else
#endif
        {
            swhdr_offset += SDPCM_HWEXT_LEN;
            hwheader1 = (pkt_len - SDPCM_FRAMETAG_LEN - tail_padding) |
                        (last_chained_pkt << 0x18);
            hwheader2 = (tail_padding) << 0x10;
            htol32_ua_store(hwheader1, frame + SDPCM_FRAMETAG_LEN);
            htol32_ua_store(hwheader2, frame + SDPCM_FRAMETAG_LEN + 0x4);
        }
    }
    PKTSETLEN((osh), (pkt), (pkt_len));

    /* software frame tags */
    swheader = ((chan << SDPCM_CHANNEL_SHIFT) & SDPCM_CHANNEL_MASK) |
               (txseq % SDPCM_SEQUENCE_WRAP) |
               (((head_padding + sdpcm_hdrlen) << SDPCM_DOFFSET_SHIFT) &
                SDPCM_DOFFSET_MASK);
    htol32_ua_store(swheader, frame + swhdr_offset);
    htol32_ua_store(0, frame + swhdr_offset + sizeof(swheader));

    return pkt_len;
}

static int dhdsdio_txpkt_postprocess(dhd_bus_t *bus, void *pkt)
{
    osl_t *osh;
    uint8 *frame;
    int data_offset;
    int tail_padding;
    int swhdr_offset =
        SDPCM_FRAMETAG_LEN + (bus->txglom_enable ? SDPCM_HWEXT_LEN : 0);

    (void)osh;
    osh = bus->dhd->osh;

    /* restore pkt buffer pointer, but keeps the header pushed by
     * dhd_prot_hdrpush */
    frame = (uint8 *)PKTDATA(osh, pkt);

    DHD_INFO(
        ("%s PKTLEN before postprocess %d", __FUNCTION__, PKTLEN(osh, pkt)));

    /* PKTLEN still includes tail_padding, so exclude it.
     * We shall have head_padding + original pkt_len for PKTLEN afterwards.
     */
    if (bus->txglom_enable) {
        /* txglom pkts have tail_padding length in HW ext header */
        tail_padding = ltoh32_ua(frame + SDPCM_FRAMETAG_LEN + 0x4) >> 0x10;
        PKTSETLEN(osh, pkt, PKTLEN(osh, pkt) - tail_padding);
        DHD_INFO((" txglom pkt: tail_padding %d PKTLEN %d\n", tail_padding,
                  PKTLEN(osh, pkt)));
    } else {
        /* non-txglom pkts have head_padding + original pkt length in HW frame
         * tag. We cannot refer to this field for txglom pkts as the first pkt
         * of the chain will have the field for the total length of the chain.
         */
        PKTSETLEN(osh, pkt, *(uint16 *)frame);
        DHD_INFO((" non-txglom pkt: HW frame tag len %d after PKTLEN %d\n",
                  *(uint16 *)frame, PKTLEN(osh, pkt)));
    }

    data_offset = ltoh32_ua(frame + swhdr_offset);
    data_offset = (data_offset & SDPCM_DOFFSET_MASK) >> SDPCM_DOFFSET_SHIFT;
    /* Get rid of sdpcm header + head_padding */
    PKTPULL(osh, pkt, data_offset);

    DHD_INFO(("%s data_offset %d, PKTLEN %d\n", __FUNCTION__, data_offset,
              PKTLEN(osh, pkt)));

    return BCME_OK;
}

static int dhdsdio_txpkt(dhd_bus_t *bus, uint chan, void **pkts, int num_pkt,
                         bool free_pkt)
{
    int i;
    int ret = 0;
    osl_t *osh;
    bcmsdh_info_t *sdh;
    void *pkt = NULL;
    void *pkt_chain;
    int total_len = 0;
    void *head_pkt = NULL;
    void *prev_pkt = NULL;
    int pad_pkt_len = 0;
    int new_pkt_num = 0;
    void *new_pkts[MAX_TX_PKTCHAIN_CNT];
    bool wlfc_enabled = FALSE;

    if (bus->dhd->dongle_reset) {
        return BCME_NOTREADY;
    }

    if (num_pkt <= 0) {
        return BCME_BADARG;
    }

    sdh = bus->sdh;
    osh = bus->dhd->osh;
    /* init new_pkts[0] to make some compiler happy, not necessary as we check
     * new_pkt_num */
    new_pkts[0] = NULL;

    for (i = 0; i < num_pkt; i++) {
        int pkt_len;
        bool last_pkt;
        void *new_pkt = NULL;

        pkt = pkts[i];
        ASSERT(pkt);
        last_pkt = (i == num_pkt - 1);
        pkt_len =
            dhdsdio_txpkt_preprocess(bus, pkt, chan, bus->tx_seq + i, total_len,
                                     last_pkt, &pad_pkt_len, &new_pkt
#if defined(BCMSDIOH_TXGLOM_EXT)
                                     ,
                                     i
#endif
            );
        if (pkt_len <= 0) {
            goto done;
        }
        if (new_pkt) {
            pkt = new_pkt;
            new_pkts[new_pkt_num++] = new_pkt;
        }
        total_len += pkt_len;

        PKTSETNEXT(osh, pkt, NULL);
        /* insert the packet into the list */
        head_pkt ? PKTSETNEXT(osh, prev_pkt, pkt) : (head_pkt = pkt);
        prev_pkt = pkt;
    }

    /* Update the HW frame tag (total length) in the first pkt of the glom */
    if (bus->txglom_enable) {
        uint8 *frame;

        total_len += pad_pkt_len;
        frame = (uint8 *)PKTDATA(osh, head_pkt);
        *(uint16 *)frame = (uint16)htol16(total_len);
        *(((uint16 *)frame) + 1) = (uint16)htol16(~total_len);
    }

#ifdef DHDENABLE_TAILPAD
    /* if a padding packet if needed, insert it to the end of the link list */
    if (pad_pkt_len) {
        PKTSETLEN(osh, bus->pad_pkt, pad_pkt_len);
        PKTSETNEXT(osh, pkt, bus->pad_pkt);
    }
#endif /* DHDENABLE_TAILPAD */

    /* dhd_bcmsdh_send_buf ignores the buffer pointer if he packet
     * parameter is not NULL, for non packet chian we pass NULL pkt pointer
     * so it will take the aligned length and buffer pointer.
     */
    pkt_chain = PKTNEXT(osh, head_pkt) ? head_pkt : NULL;
#ifdef TPUT_MONITOR
    if ((bus->dhd->conf->data_drop_mode == TXPKT_DROP) && (total_len > 0x1F4)) {
        ret = BCME_OK;
    } else
#endif
        ret = dhd_bcmsdh_send_buf(bus, bcmsdh_cur_sbwad(sdh), SDIO_FUNC_2,
                                  F2SYNC, PKTDATA(osh, head_pkt), total_len,
                                  pkt_chain, NULL, NULL, TXRETRIES);
    if (ret == BCME_OK) {
        bus->tx_seq = (bus->tx_seq + num_pkt) % SDPCM_SEQUENCE_WRAP;
    }

    /* if a padding packet was needed, remove it from the link list as it not a
     * data pkt */
    if (pad_pkt_len && pkt) {
        PKTSETNEXT(osh, pkt, NULL);
    }

done:
    pkt = head_pkt;
    while (pkt) {
        void *pkt_next = PKTNEXT(osh, pkt);
        PKTSETNEXT(osh, pkt, NULL);
        dhdsdio_txpkt_postprocess(bus, pkt);
        pkt = pkt_next;
    }

    /* new packets might be allocated due to insufficient room for padding, but
     * we still have to indicate the original packets to upper layer
     */
    for (i = 0; i < num_pkt; i++) {
        pkt = pkts[i];
        wlfc_enabled = FALSE;
#ifdef PROP_TXSTATUS
        if (DHD_PKTTAG_WLFCPKT(PKTTAG(pkt))) {
            wlfc_enabled = (dhd_wlfc_txcomplete(bus->dhd, pkt, ret == 0) !=
                            WLFC_UNSUPPORTED);
        }
#endif /* PROP_TXSTATUS */
        if (!wlfc_enabled) {
            PKTSETNEXT(osh, pkt, NULL);
            dhd_txcomplete(bus->dhd, pkt, ret != 0);
            if (free_pkt) {
                PKTFREE(osh, pkt, TRUE);
            }
        }
    }

    for (i = 0; i < new_pkt_num; i++) {
        PKTFREE(osh, new_pkts[i], TRUE);
    }

    return ret;
}

static uint dhdsdio_sendfromq(dhd_bus_t *bus, uint maxframes)
{
    uint cnt = 0;
    uint8 tx_prec_map;
    uint16 txpktqlen = 0;
    uint32 intstatus = 0;
    uint retries = 0;
    osl_t *osh;
    dhd_pub_t *dhd = bus->dhd;
    sdpcmd_regs_t *regs = bus->regs;
#if defined(DHD_LOSSLESS_ROAMING) || defined(DHD_PKTDUMP_TOFW)
    uint8 *pktdata;
    struct ether_header *eh;
#ifdef BDC
    struct bdc_header *bdc_header;
    uint8 data_offset;
#endif // endif
#endif /* DHD_LOSSLESS_ROAMING */

    DHD_TRACE(("%s: Enter\n", __FUNCTION__));

    if (!KSO_ENAB(bus)) {
        DHD_ERROR(("%s: Device asleep\n", __FUNCTION__));
        return BCME_NODEVICE;
    }

    osh = dhd->osh;
    tx_prec_map = ~bus->flowcontrol;
#ifdef DHD_LOSSLESS_ROAMING
    tx_prec_map &= dhd->dequeue_prec_map;
#endif /* DHD_LOSSLESS_ROAMING */
    for (cnt = 0; (cnt < maxframes) && DATAOK(bus);) {
        int i;
        int num_pkt = 1;
        void *pkts[MAX_TX_PKTCHAIN_CNT];
        int prec_out;
        uint datalen = 0;

        dhd_os_sdlock_txq(bus->dhd);
        if (bus->txglom_enable) {
            uint32 glomlimit = (uint32)bus->txglomsize;
#if defined(BCMSDIOH_STD)
            if (bus->blocksize == 0x40) {
                glomlimit = MIN((uint32)bus->txglomsize, BLK_64_MAXTXGLOM);
            }
#endif /* BCMSDIOH_STD */
            num_pkt = MIN((uint32)DATABUFCNT(bus), glomlimit);
            num_pkt = MIN(num_pkt, ARRAYSIZE(pkts));
        }
        num_pkt = MIN(num_pkt, pktq_mlen(&bus->txq, tx_prec_map));
        for (i = 0; i < num_pkt; i++) {
            pkts[i] = pktq_mdeq(&bus->txq, tx_prec_map, &prec_out);
            if (!pkts[i]) {
                DHD_ERROR(
                    ("%s: pktq_mlen non-zero when no pkt\n", __FUNCTION__));
                ASSERT(0);
                break;
            }
#if defined(DHD_LOSSLESS_ROAMING) || defined(DHD_PKTDUMP_TOFW)
            pktdata = (uint8 *)PKTDATA(osh, pkts[i]);
#ifdef BDC
            /* Skip BDC header */
            bdc_header = (struct bdc_header *)pktdata;
            data_offset = bdc_header->dataOffset;
            pktdata += BDC_HEADER_LEN + (data_offset << 0x2);
#endif // endif
            eh = (struct ether_header *)pktdata;
#ifdef DHD_LOSSLESS_ROAMING
            if (eh->ether_type == hton16(ETHER_TYPE_802_1X)) {
                uint8 prio = (uint8)PKTPRIO(pkts[i]);
                /* Restore to original priority for 802.1X packet */
                if (prio == PRIO_8021D_NC) {
                    PKTSETPRIO(pkts[i], dhd->prio_8021x);
#ifdef BDC
                    /* Restore to original priority in BDC header */
                    bdc_header->priority =
                        (dhd->prio_8021x & BDC_PRIORITY_MASK);
#endif // endif
                }
            }
#endif /* DHD_LOSSLESS_ROAMING */
#ifdef DHD_PKTDUMP_TOFW
            dhd_dump_pkt(bus->dhd, BDC_GET_IF_IDX(bdc_header), pktdata,
                         (uint32)PKTLEN(bus->dhd->osh, pkts[i]), TRUE, NULL,
                         NULL);
#endif
#endif /* DHD_LOSSLESS_ROAMING || DHD_8021X_DUMP */
            if (!bus->dhd->conf->orphan_move) {
                PKTORPHAN(pkts[i], bus->dhd->conf->tsq);
            }
            datalen += PKTLEN(osh, pkts[i]);
        }
        dhd_os_sdunlock_txq(bus->dhd);

        if (i == 0) {
            break;
        }
        if (dhdsdio_txpkt(bus, SDPCM_DATA_CHANNEL, pkts, i, TRUE) != BCME_OK) {
            dhd->tx_errors++;
        } else {
            dhd->dstats.tx_bytes += datalen;
            bus->txglomframes++;
            bus->txglompkts += num_pkt;
#ifdef PKT_STATICS
            bus->tx_statics.glom_cnt_us[num_pkt - 1] =
                (bus->tx_statics.glom_cnt[num_pkt - 1] *
                     bus->tx_statics.glom_cnt_us[num_pkt - 1] +
                 bcmsdh_get_spend_time(bus->sdh)) /
                (bus->tx_statics.glom_cnt[num_pkt - 1] + 1);
#endif
        }
        cnt += i;
#ifdef PKT_STATICS
        if (num_pkt) {
            bus->tx_statics.glom_cnt[num_pkt - 1]++;
            if (num_pkt > bus->tx_statics.glom_max) {
                bus->tx_statics.glom_max = num_pkt;
            }
        }
#endif

        /* In poll mode, need to check for other events */
        if (!bus->intr && cnt) {
            /* Check device status, signal pending interrupt */
            R_SDREG(intstatus, &regs->intstatus, retries);
            bus->f2txdata++;
            if (bcmsdh_regfail(bus->sdh)) {
                break;
            }
            if (intstatus & bus->hostintmask) {
                bus->ipend = TRUE;
            }
        }
    }

    if (dhd_doflow) {
        dhd_os_sdlock_txq(bus->dhd);
        txpktqlen = pktq_n_pkts_tot(&bus->txq);
        dhd_os_sdunlock_txq(bus->dhd);
    }

    /* Do flow-control if needed */
    if (dhd->up && (dhd->busstate == DHD_BUS_DATA) && (txpktqlen < FCLOW)) {
        bool wlfc_enabled = FALSE;
#ifdef PROP_TXSTATUS
        wlfc_enabled =
            (dhd_wlfc_flowcontrol(dhd, OFF, TRUE) != WLFC_UNSUPPORTED);
#endif // endif
        if (!wlfc_enabled && dhd_doflow && dhd->txoff) {
            dhd_txflowcontrol(dhd, ALL_INTERFACES, OFF);
        }
    }

    return cnt;
}

static void dhdsdio_sendpendctl(dhd_bus_t *bus)
{
    bcmsdh_info_t *sdh = bus->sdh;
    int ret;
    uint8 *frame_seq = bus->ctrl_frame_buf + SDPCM_FRAMETAG_LEN;

    if (bus->txglom_enable) {
        frame_seq += SDPCM_HWEXT_LEN;
    }

    if (*frame_seq != bus->tx_seq) {
        DHD_INFO(("%s IOCTL frame seq lag detected!"
                  " frm_seq:%d != bus->tx_seq:%d, corrected\n",
                  __FUNCTION__, *frame_seq, bus->tx_seq));
        *frame_seq = bus->tx_seq;
    }

    ret = dhd_bcmsdh_send_buf(bus, bcmsdh_cur_sbwad(sdh), SDIO_FUNC_2, F2SYNC,
                              (uint8 *)bus->ctrl_frame_buf,
                              (uint32)bus->ctrl_frame_len, NULL, NULL, NULL, 1);
    if (ret == BCME_OK) {
        bus->tx_seq = (bus->tx_seq + 1) % SDPCM_SEQUENCE_WRAP;
    }

    bus->ctrl_frame_stat = FALSE;
    dhd_wait_event_wakeup(bus->dhd);
}

int dhd_bus_txctl(struct dhd_bus *bus, uchar *msg, uint msglen)
{
    static int err_nodevice = 0;
    uint8 *frame;
    uint16 len;
    uint32 swheader;
    uint8 doff = 0;
    int ret = -1;
    uint8 sdpcm_hdrlen =
        bus->txglom_enable ? SDPCM_HDRLEN_TXGLOM : SDPCM_HDRLEN;

    DHD_TRACE(("%s: Enter\n", __FUNCTION__));

    if (bus->dhd->dongle_reset) {
        return -EIO;
    }

    /* Back the pointer to make a room for bus header */
    frame = msg - sdpcm_hdrlen;
    len = (msglen += sdpcm_hdrlen);

    /* Add alignment padding (optional for ctl frames) */
    if (dhd_alignctl) {
        if ((doff = ((uintptr)frame % DHD_SDALIGN))) {
            frame -= doff;
            len += doff;
            msglen += doff;
            bzero(frame, doff + sdpcm_hdrlen);
        }
        ASSERT(doff < DHD_SDALIGN);
    }
    doff += sdpcm_hdrlen;

#ifndef BCMSPI
    /* Round send length to next SDIO block */
    if (bus->roundup && bus->blocksize && (len > bus->blocksize)) {
        uint16 pad = bus->blocksize - (len % bus->blocksize);
        if ((pad <= bus->roundup) && (pad < bus->blocksize)) {
            len += pad;
        }
    } else if (len % DHD_SDALIGN) {
        len += DHD_SDALIGN - (len % DHD_SDALIGN);
    }
#endif /* BCMSPI */

    /* Satisfy length-alignment requirements */
    if (forcealign && (len & (ALIGNMENT - 1))) {
        len = ROUNDUP(len, ALIGNMENT);
    }

    ASSERT(ISALIGNED((uintptr)frame, 0x2));

    /* Need to lock here to protect txseq and SDIO tx calls */
    dhd_os_sdlock(bus->dhd);
    if (bus->dhd->conf->txctl_tmo_fix > 0 && !TXCTLOK(bus)) {
        bus->ctrl_wait = TRUE;
        dhd_os_sdunlock(bus->dhd);
        wait_event_interruptible_timeout(
            bus->ctrl_tx_wait, TXCTLOK(bus),
            msecs_to_jiffies(bus->dhd->conf->txctl_tmo_fix));
        dhd_os_sdlock(bus->dhd);
        bus->ctrl_wait = FALSE;
    }

    BUS_WAKE(bus);

    /* Make sure backplane clock is on */
    dhdsdio_clkctl(bus, CLK_AVAIL, FALSE);

    /* Hardware tag: 2 byte len followed by 2 byte ~len check (all LE) */
    *(uint16 *)frame = htol16((uint16)msglen);
    *(((uint16 *)frame) + 1) = htol16(~msglen);

    if (bus->txglom_enable) {
        uint32 hwheader1, hwheader2;
        /* Software tag: channel, sequence number, data offset */
        swheader = ((SDPCM_CONTROL_CHANNEL << SDPCM_CHANNEL_SHIFT) &
                    SDPCM_CHANNEL_MASK) |
                   bus->tx_seq |
                   ((doff << SDPCM_DOFFSET_SHIFT) & SDPCM_DOFFSET_MASK);
        htol32_ua_store(swheader, frame + SDPCM_FRAMETAG_LEN + SDPCM_HWEXT_LEN);
        htol32_ua_store(0, frame + SDPCM_FRAMETAG_LEN + SDPCM_HWEXT_LEN +
                               sizeof(swheader));

        hwheader1 = (msglen - SDPCM_FRAMETAG_LEN) | (1 << 0x18);
        hwheader2 = (len - (msglen)) << 0x10;
        htol32_ua_store(hwheader1, frame + SDPCM_FRAMETAG_LEN);
        htol32_ua_store(hwheader2, frame + SDPCM_FRAMETAG_LEN + 0x4);

        *(uint16 *)frame = htol16(len);
        *(((uint16 *)frame) + 1) = htol16(~(len));
    } else {
        /* Software tag: channel, sequence number, data offset */
        swheader = ((SDPCM_CONTROL_CHANNEL << SDPCM_CHANNEL_SHIFT) &
                    SDPCM_CHANNEL_MASK) |
                   bus->tx_seq |
                   ((doff << SDPCM_DOFFSET_SHIFT) & SDPCM_DOFFSET_MASK);
        htol32_ua_store(swheader, frame + SDPCM_FRAMETAG_LEN);
        htol32_ua_store(0, frame + SDPCM_FRAMETAG_LEN + sizeof(swheader));
    }

#ifdef DHD_ULP
    dhd_ulp_set_path(bus->dhd, DHD_ULP_TX_CTRL);

    if (!TXCTLOK(bus) || !dhd_ulp_f2_ready(bus->dhd, bus->sdh))
#else
    if (!TXCTLOK(bus))
#endif // endif
    {
        DHD_INFO(("%s: No bus credit bus->tx_max %d, bus->tx_seq %d\n",
                  __FUNCTION__, bus->tx_max, bus->tx_seq));
        bus->ctrl_frame_stat = TRUE;
        /* Send from dpc */
        bus->ctrl_frame_buf = frame;
        bus->ctrl_frame_len = len;

        if (!bus->dpc_sched) {
            bus->dpc_sched = TRUE;
            dhd_sched_dpc(bus->dhd);
        }
        if (bus->ctrl_frame_stat) {
            dhd_wait_for_event(bus->dhd, &bus->ctrl_frame_stat);
        }

        if (bus->ctrl_frame_stat == FALSE) {
            DHD_INFO(("%s: ctrl_frame_stat == FALSE\n", __FUNCTION__));
            ret = 0;
        } else {
            bus->dhd->txcnt_timeout++;
            if (!bus->dhd->hang_was_sent) {
                DHD_ERROR(("%s: ctrl_frame_stat == TRUE txcnt_timeout=%d\n",
                           __FUNCTION__, bus->dhd->txcnt_timeout));
#ifdef BCMSDIO_RXLIM_POST
                DHD_ERROR(("%s: rxlim_en=%d, rxlim enable=%d, rxlim_addr=%d\n",
                           __FUNCTION__, bus->dhd->conf->rxlim_en,
                           bus->rxlim_en, bus->rxlim_addr));
#endif /* BCMSDIO_RXLIM_POST */
            }
#ifdef DHD_FW_COREDUMP
            /* Collect socram dump */
            if ((bus->dhd->memdump_enabled) &&
                (bus->dhd->txcnt_timeout >= MAX_CNTL_TX_TIMEOUT)) {
                /* collect core dump */
                bus->dhd->memdump_type = DUMP_TYPE_RESUMED_ON_TIMEOUT_TX;
                dhd_os_sdunlock(bus->dhd);
                dhd_bus_mem_dump(bus->dhd);
                dhd_os_sdlock(bus->dhd);
            }
#endif /* DHD_FW_COREDUMP */
            ret = -1;
            bus->ctrl_frame_stat = FALSE;
            goto done;
        }
    }

    bus->dhd->txcnt_timeout = 0;
    bus->ctrl_frame_stat = TRUE;

    if (ret == -1) {
#ifdef DHD_DEBUG
        if (DHD_BYTES_ON() && DHD_CTL_ON()) {
            prhex("Tx Frame", frame, len);
        } else if (DHD_HDRS_ON()) {
            prhex("TxHdr", frame, MIN(len, 0x10));
        }
#endif // endif
#ifdef PKT_STATICS
        bus->tx_statics.ctrl_count++;
        bus->tx_statics.ctrl_size += len;
#endif
        ret = dhd_bcmsdh_send_buffer(bus, frame, len);
    }
    bus->ctrl_frame_stat = FALSE;
#ifdef DHD_ULP
    dhd_ulp_enable_cached_sbwad(bus->dhd, bus->sdh);
#endif /* DHD_ULP */

done:
    if ((bus->idletime == DHD_IDLE_IMMEDIATE) && !bus->dpc_sched &&
        NO_OTHER_ACTIVE_BUS_USER(bus)) {
        bus->activity = FALSE;
        dhdsdio_bussleep(bus, TRUE);
        dhdsdio_clkctl(bus, CLK_NONE, FALSE);
    }

    dhd_os_sdunlock(bus->dhd);

    if (ret) {
        bus->dhd->tx_ctlerrs++;
    } else {
        bus->dhd->tx_ctlpkts++;
    }

    if (bus->dhd->txcnt_timeout >= MAX_CNTL_TX_TIMEOUT) {
#ifdef DHD_PM_CONTROL_FROM_FILE
        if (g_pm_control == TRUE) {
            return -BCME_ERROR;
        } else {
            return -ETIMEDOUT;
        }
#else
        return -ETIMEDOUT;
#endif /* DHD_PM_CONTROL_FROM_FILE */
    }
    if (ret == BCME_NODEVICE) {
        err_nodevice++;
    } else {
        err_nodevice = 0;
    }

    return ret ? err_nodevice >= ERROR_BCME_NODEVICE_MAX ? -ETIMEDOUT : -EIO
               : 0;
}

int dhd_bus_rxctl(struct dhd_bus *bus, uchar *msg, uint msglen)
{
    int timeleft;
    uint rxlen = 0;
    static uint cnt = 0;
    uint max_rxcnt;

    DHD_TRACE(("%s: Enter\n", __FUNCTION__));

    if (bus->dhd->dongle_reset) {
        return -EIO;
    }

    /* Wait until control frame is available */
    timeleft = dhd_os_ioctl_resp_wait(bus->dhd, &bus->rxlen);

    dhd_os_sdlock(bus->dhd);
    rxlen = bus->rxlen;
    bcopy(bus->rxctl, msg, MIN(msglen, rxlen));
    bus->rxlen = 0;
    dhd_os_sdunlock(bus->dhd);

    if (bus->dhd->conf->ctrl_resched > 0 && !rxlen && timeleft == 0) {
        cnt++;
        if (cnt <= bus->dhd->conf->ctrl_resched) {
            uint32 status, retry = 0;
            R_SDREG(status, &bus->regs->intstatus, retry);
            if ((status & I_HMB_HOST_INT) || PKT_AVAILABLE(bus, status)) {
                DHD_ERROR(("%s: reschedule dhd_dpc, cnt=%d, status=0x%x\n",
                           __FUNCTION__, cnt, status));
                bus->ipend = TRUE;
                bus->dpc_sched = TRUE;
                dhd_sched_dpc(bus->dhd);

                /* Wait until control frame is available */
                timeleft = dhd_os_ioctl_resp_wait(bus->dhd, &bus->rxlen);

                dhd_os_sdlock(bus->dhd);
                rxlen = bus->rxlen;
                bcopy(bus->rxctl, msg, MIN(msglen, rxlen));
                bus->rxlen = 0;
                dhd_os_sdunlock(bus->dhd);
            }
        }
    } else {
        cnt = 0;
    }

    if (rxlen) {
        DHD_CTL(("%s: resumed on rxctl frame, got %d expected %d\n",
                 __FUNCTION__, rxlen, msglen));
    } else {
        if (timeleft == 0) {
#ifdef DHD_DEBUG
            uint32 status, retry = 0;
            R_SDREG(status, &bus->regs->intstatus, retry);
            DHD_ERROR(("%s: resumed on timeout, INT status=0x%08X\n",
                       __FUNCTION__, status));
#else
            DHD_ERROR(("%s: resumed on timeout\n", __FUNCTION__));
#endif /* DHD_DEBUG */
            if (!bus->dhd->dongle_trap_occured) {
#ifdef DHD_FW_COREDUMP
                bus->dhd->memdump_type = DUMP_TYPE_RESUMED_ON_TIMEOUT;
#endif /* DHD_FW_COREDUMP */
                dhd_os_sdlock(bus->dhd);
                dhdsdio_checkdied(bus, NULL, 0);
                dhd_os_sdunlock(bus->dhd);
            }
        } else {
            DHD_CTL(("%s: resumed for unknown reason?\n", __FUNCTION__));
            if (!bus->dhd->dongle_trap_occured) {
#ifdef DHD_FW_COREDUMP
                bus->dhd->memdump_type = DUMP_TYPE_RESUMED_UNKNOWN;
#endif /* DHD_FW_COREDUMP */
                dhd_os_sdlock(bus->dhd);
                dhdsdio_checkdied(bus, NULL, 0);
                dhd_os_sdunlock(bus->dhd);
            }
        }
#ifdef DHD_FW_COREDUMP
        /* Dump the ram image */
        if (bus->dhd->memdump_enabled && !bus->dhd->dongle_trap_occured) {
            dhdsdio_mem_dump(bus);
        }
#endif /* DHD_FW_COREDUMP */
    }
    if (timeleft == 0) {
        if (rxlen == 0) {
            bus->dhd->rxcnt_timeout++;
        }
        DHD_ERROR(("%s: rxcnt_timeout=%d, rxlen=%d\n", __FUNCTION__,
                   bus->dhd->rxcnt_timeout, rxlen));
#ifdef DHD_FW_COREDUMP
        /* collect socram dump */
        if (bus->dhd->memdump_enabled) {
            bus->dhd->memdump_type = DUMP_TYPE_RESUMED_ON_TIMEOUT_RX;
            dhd_bus_mem_dump(bus->dhd);
        }
#endif /* DHD_FW_COREDUMP */
    } else {
        bus->dhd->rxcnt_timeout = 0;
    }

    if (rxlen) {
        bus->dhd->rx_ctlpkts++;
    } else {
        bus->dhd->rx_ctlerrs++;
    }

    if (bus->dhd->conf->rxcnt_timeout) {
        max_rxcnt = bus->dhd->conf->rxcnt_timeout;
    } else {
        max_rxcnt = MAX_CNTL_RX_TIMEOUT;
    }
    if (bus->dhd->rxcnt_timeout >= max_rxcnt) {
#ifdef DHD_PM_CONTROL_FROM_FILE
        if (g_pm_control == TRUE) {
            return -BCME_ERROR;
        } else {
            return -ETIMEDOUT;
        }
#else
        return -ETIMEDOUT;
#endif /* DHD_PM_CONTROL_FROM_FILE */
    }
    if (bus->dhd->dongle_trap_occured) {
        return -EREMOTEIO;
    }

    return rxlen ? (int)rxlen : -EIO;
}

/* IOVar table */
enum {
    IOV_INTR = 1,
    IOV_POLLRATE,
    IOV_SDREG,
    IOV_SBREG,
    IOV_SDCIS,
    IOV_RAMSIZE,
    IOV_RAMSTART,
#ifdef DHD_DEBUG
    IOV_CHECKDIED,
    IOV_SERIALCONS,
#endif /* DHD_DEBUG */
    IOV_SET_DOWNLOAD_STATE,
    IOV_SOCRAM_STATE,
    IOV_FORCEEVEN,
    IOV_SDIOD_DRIVE,
    IOV_READAHEAD,
    IOV_SDRXCHAIN,
    IOV_ALIGNCTL,
    IOV_SDALIGN,
    IOV_DEVRESET,
    IOV_CPU,
#if defined(USE_SDIOFIFO_IOVAR)
    IOV_WATERMARK,
    IOV_MESBUSYCTRL,
#endif /* USE_SDIOFIFO_IOVAR */
#ifdef SDTEST
    IOV_PKTGEN,
    IOV_EXTLOOP,
#endif /* SDTEST */
    IOV_SPROM,
    IOV_TXBOUND,
    IOV_RXBOUND,
    IOV_TXMINMAX,
    IOV_IDLETIME,
    IOV_IDLECLOCK,
    IOV_SD1IDLE,
    IOV_SLEEP,
    IOV_DONGLEISOLATION,
    IOV_KSO,
    IOV_DEVSLEEP,
    IOV_DEVCAP,
    IOV_VARS,
#ifdef SOFTAP
    IOV_FWPATH,
#endif // endif
    IOV_TXGLOMSIZE,
    IOV_TXGLOMMODE,
    IOV_HANGREPORT,
    IOV_TXINRX_THRES,
    IOV_SDIO_SUSPEND
#if defined(DEBUGGER) || defined(DHD_DSCOPE)
        IOV_GDB_SERVER, /**< starts gdb server on given interface */
#endif                  /* DEBUGGER || DHD_DSCOPE */
};

const bcm_iovar_t dhdsdio_iovars[] = {
    {"intr", IOV_INTR, 0, 0, IOVT_BOOL, 0},
    {"sleep", IOV_SLEEP, 0, 0, IOVT_BOOL, 0},
    {"pollrate", IOV_POLLRATE, 0, 0, IOVT_UINT32, 0},
    {"idletime", IOV_IDLETIME, 0, 0, IOVT_INT32, 0},
    {"idleclock", IOV_IDLECLOCK, 0, 0, IOVT_INT32, 0},
    {"sd1idle", IOV_SD1IDLE, 0, 0, IOVT_BOOL, 0},
    {"ramsize", IOV_RAMSIZE, 0, 0, IOVT_UINT32, 0},
    {"ramstart", IOV_RAMSTART, 0, 0, IOVT_UINT32, 0},
    {"dwnldstate", IOV_SET_DOWNLOAD_STATE, 0, 0, IOVT_BOOL, 0},
    {"socram_state", IOV_SOCRAM_STATE, 0, 0, IOVT_BOOL, 0},
    {"vars", IOV_VARS, 0, 0, IOVT_BUFFER, 0},
    {"sdiod_drive", IOV_SDIOD_DRIVE, 0, 0, IOVT_UINT32, 0},
    {"readahead", IOV_READAHEAD, 0, 0, IOVT_BOOL, 0},
    {"sdrxchain", IOV_SDRXCHAIN, 0, 0, IOVT_BOOL, 0},
    {"alignctl", IOV_ALIGNCTL, 0, 0, IOVT_BOOL, 0},
    {"sdalign", IOV_SDALIGN, 0, 0, IOVT_BOOL, 0},
    {"devreset", IOV_DEVRESET, 0, 0, IOVT_BOOL, 0},
#ifdef DHD_DEBUG
    {"sdreg", IOV_SDREG, 0, 0, IOVT_BUFFER, sizeof(sdreg_t)},
    {"sbreg", IOV_SBREG, 0, 0, IOVT_BUFFER, sizeof(sdreg_t)},
    {"sd_cis", IOV_SDCIS, 0, 0, IOVT_BUFFER, DHD_IOCTL_MAXLEN},
    {"forcealign", IOV_FORCEEVEN, 0, 0, IOVT_BOOL, 0},
    {"txbound", IOV_TXBOUND, 0, 0, IOVT_UINT32, 0},
    {"rxbound", IOV_RXBOUND, 0, 0, IOVT_UINT32, 0},
    {"txminmax", IOV_TXMINMAX, 0, 0, IOVT_UINT32, 0},
    {"cpu", IOV_CPU, 0, 0, IOVT_BOOL, 0},
#ifdef DHD_DEBUG
    {"checkdied", IOV_CHECKDIED, 0, 0, IOVT_BUFFER, 0},
    {"serial", IOV_SERIALCONS, 0, 0, IOVT_UINT32, 0},
#endif /* DHD_DEBUG  */
#endif /* DHD_DEBUG */
#ifdef SDTEST
    {"extloop", IOV_EXTLOOP, 0, 0, IOVT_BOOL, 0},
    {"pktgen", IOV_PKTGEN, 0, 0, IOVT_BUFFER, sizeof(dhd_pktgen_t)},
#endif /* SDTEST */
#if defined(USE_SDIOFIFO_IOVAR)
    {"watermark", IOV_WATERMARK, 0, 0, IOVT_UINT32, 0},
    {"mesbusyctrl", IOV_MESBUSYCTRL, 0, 0, IOVT_UINT32, 0},
#endif /* USE_SDIOFIFO_IOVAR */
    {"devcap", IOV_DEVCAP, 0, 0, IOVT_UINT32, 0},
    {"dngl_isolation", IOV_DONGLEISOLATION, 0, 0, IOVT_UINT32, 0},
    {"kso", IOV_KSO, 0, 0, IOVT_UINT32, 0},
    {"devsleep", IOV_DEVSLEEP, 0, 0, IOVT_UINT32, 0},
#ifdef SOFTAP
    {"fwpath", IOV_FWPATH, 0, 0, IOVT_BUFFER, 0},
#endif // endif
    {"txglomsize", IOV_TXGLOMSIZE, 0, 0, IOVT_UINT32, 0},
    {"fw_hang_report", IOV_HANGREPORT, 0, 0, IOVT_BOOL, 0},
    {"txinrx_thres", IOV_TXINRX_THRES, 0, 0, IOVT_INT32, 0},
    {"sdio_suspend", IOV_SDIO_SUSPEND, 0, 0, IOVT_UINT32, 0},
#if defined(DEBUGGER) || defined(DHD_DSCOPE)
    {"gdb_server", IOV_GDB_SERVER, 0, 0, IOVT_UINT32, 0},
#endif /* DEBUGGER || DHD_DSCOPE */
    {NULL, 0, 0, 0, 0, 0}};

static void dhd_dump_pct(struct bcmstrbuf *strbuf, char *desc, uint num,
                         uint div)
{
    uint q1, q2;

    if (!div) {
        bcm_bprintf(strbuf, "%s N/A", desc);
    } else {
        q1 = num / div;
        q2 = (0x64 * (num - (q1 * div))) / div;
        bcm_bprintf(strbuf, "%s %d.%02d", desc, q1, q2);
    }
}

void dhd_bus_dump(dhd_pub_t *dhdp, struct bcmstrbuf *strbuf)
{
    dhd_bus_t *bus = dhdp->bus;
#if defined(DHD_WAKE_STATUS) && defined(DHD_WAKE_EVENT_STATUS)
    int i;
#endif // endif

    bcm_bprintf(strbuf, "Bus SDIO structure:\n");
    bcm_bprintf(strbuf, "hostintmask 0x%08x intstatus 0x%08x sdpcm_ver %d\n",
                bus->hostintmask, bus->intstatus, bus->sdpcm_ver);
    bcm_bprintf(
        strbuf,
        "fcstate %d qlen %u tx_seq %d, max %d, rxskip %d rxlen %u rx_seq %d\n",
        bus->fcstate, pktq_n_pkts_tot(&bus->txq), bus->tx_seq, bus->tx_max,
        bus->rxskip, bus->rxlen, bus->rx_seq);
    bcm_bprintf(strbuf, "intr %d intrcount %u lastintrs %u spurious %u\n",
                bus->intr, bus->intrcount, bus->lastintrs, bus->spurious);

#ifdef DHD_WAKE_STATUS
    bcm_bprintf(strbuf, "wake %u rxwake %u readctrlwake %u\n",
                bcmsdh_get_total_wake(bus->sdh), bus->wake_counts.rxwake,
                bus->wake_counts.rcwake);
#ifdef DHD_WAKE_RX_STATUS
    bcm_bprintf(strbuf, " unicast %u multicast %u broadcast %u arp %u\n",
                bus->wake_counts.rx_ucast, bus->wake_counts.rx_mcast,
                bus->wake_counts.rx_bcast, bus->wake_counts.rx_arp);
    bcm_bprintf(strbuf, " multi4 %u multi6 %u icmp6 %u multiother %u\n",
                bus->wake_counts.rx_multi_ipv4, bus->wake_counts.rx_multi_ipv6,
                bus->wake_counts.rx_icmpv6, bus->wake_counts.rx_multi_other);
    bcm_bprintf(strbuf, " icmp6_ra %u, icmp6_na %u, icmp6_ns %u\n",
                bus->wake_counts.rx_icmpv6_ra, bus->wake_counts.rx_icmpv6_na,
                bus->wake_counts.rx_icmpv6_ns);
#endif /* DHD_WAKE_RX_STATUS */
#ifdef DHD_WAKE_EVENT_STATUS
    for (i = 0; i < WLC_E_LAST; i++) {
        if (bus->wake_counts.rc_event[i] != 0) {
            bcm_bprintf(strbuf, " %s = %u\n", bcmevent_get_name(i),
                        bus->wake_counts.rc_event[i]);
        }
    }
    bcm_bprintf(strbuf, "\n");
#endif /* DHD_WAKE_EVENT_STATUS */
#endif /* DHD_WAKE_STATUS */

    bcm_bprintf(strbuf, "pollrate %u pollcnt %u regfails %u\n", bus->pollrate,
                bus->pollcnt, bus->regfails);

    bcm_bprintf(strbuf, "\nAdditional counters:\n");
#ifdef DHDENABLE_TAILPAD
    bcm_bprintf(strbuf, "tx_tailpad_chain %u tx_tailpad_pktget %u\n",
                bus->tx_tailpad_chain, bus->tx_tailpad_pktget);
#endif /* DHDENABLE_TAILPAD */
    bcm_bprintf(
        strbuf,
        "tx_sderrs %u fcqueued %u rxrtx %u rx_toolong %u rxc_errors %u\n",
        bus->tx_sderrs, bus->fcqueued, bus->rxrtx, bus->rx_toolong,
        bus->rxc_errors);
    bcm_bprintf(strbuf, "rx_hdrfail %u badhdr %u badseq %u\n", bus->rx_hdrfail,
                bus->rx_badhdr, bus->rx_badseq);
    bcm_bprintf(strbuf, "fc_rcvd %u, fc_xoff %u, fc_xon %u\n", bus->fc_rcvd,
                bus->fc_xoff, bus->fc_xon);
    bcm_bprintf(strbuf, "rxglomfail %u, rxglomframes %u, rxglompkts %u\n",
                bus->rxglomfail, bus->rxglomframes, bus->rxglompkts);
    bcm_bprintf(strbuf, "f2rx (hdrs/data) %u (%u/%u), f2tx %u f1regs %u\n",
                (bus->f2rxhdrs + bus->f2rxdata), bus->f2rxhdrs, bus->f2rxdata,
                bus->f2txdata, bus->f1regdata);
    {
        dhd_dump_pct(strbuf, "\nRx: pkts/f2rd", bus->dhd->rx_packets,
                     (bus->f2rxhdrs + bus->f2rxdata));
        dhd_dump_pct(strbuf, ", pkts/f1sd", bus->dhd->rx_packets,
                     bus->f1regdata);
        dhd_dump_pct(strbuf, ", pkts/sd", bus->dhd->rx_packets,
                     (bus->f2rxhdrs + bus->f2rxdata + bus->f1regdata));
        dhd_dump_pct(strbuf, ", pkts/int", bus->dhd->rx_packets,
                     bus->intrcount);
        bcm_bprintf(strbuf, "\n");

        dhd_dump_pct(strbuf, "Rx: glom pct", (0x64 * bus->rxglompkts),
                     bus->dhd->rx_packets);
        dhd_dump_pct(strbuf, ", pkts/glom", bus->rxglompkts, bus->rxglomframes);
        bcm_bprintf(strbuf, "\n");

        dhd_dump_pct(strbuf, "Tx: pkts/f2wr", bus->dhd->tx_packets,
                     bus->f2txdata);
        dhd_dump_pct(strbuf, ", pkts/f1sd", bus->dhd->tx_packets,
                     bus->f1regdata);
        dhd_dump_pct(strbuf, ", pkts/sd", bus->dhd->tx_packets,
                     (bus->f2txdata + bus->f1regdata));
        dhd_dump_pct(strbuf, ", pkts/int", bus->dhd->tx_packets,
                     bus->intrcount);
        bcm_bprintf(strbuf, "\n");

        dhd_dump_pct(strbuf, "Total: pkts/f2rw",
                     (bus->dhd->tx_packets + bus->dhd->rx_packets),
                     (bus->f2txdata + bus->f2rxhdrs + bus->f2rxdata));
        dhd_dump_pct(strbuf, ", pkts/f1sd",
                     (bus->dhd->tx_packets + bus->dhd->rx_packets),
                     bus->f1regdata);
        dhd_dump_pct(
            strbuf, ", pkts/sd", (bus->dhd->tx_packets + bus->dhd->rx_packets),
            (bus->f2txdata + bus->f2rxhdrs + bus->f2rxdata + bus->f1regdata));
        dhd_dump_pct(strbuf, ", pkts/int",
                     (bus->dhd->tx_packets + bus->dhd->rx_packets),
                     bus->intrcount);
        bcm_bprintf(strbuf, "\n\n");
    }

#ifdef SDTEST
    if (bus->pktgen_count) {
        bcm_bprintf(strbuf, "pktgen config and count:\n");
        bcm_bprintf(strbuf,
                    "freq %u count %u print %u total %u min %u len %u\n",
                    bus->pktgen_freq, bus->pktgen_count, bus->pktgen_print,
                    bus->pktgen_total, bus->pktgen_minlen, bus->pktgen_maxlen);
        bcm_bprintf(strbuf, "send attempts %u rcvd %u fail %u\n",
                    bus->pktgen_sent, bus->pktgen_rcvd, bus->pktgen_fail);
    }
#endif /* SDTEST */
#ifdef DHD_DEBUG
    bcm_bprintf(strbuf, "dpc_sched %d host interrupt%spending\n",
                bus->dpc_sched,
                (bcmsdh_intr_pending(bus->sdh) ? " " : " not "));
    bcm_bprintf(strbuf, "blocksize %u roundup %u\n", bus->blocksize,
                bus->roundup);
#endif /* DHD_DEBUG */
    bcm_bprintf(
        strbuf,
        "clkstate %d activity %d idletime %d idlecount %d sleeping %d\n",
        bus->clkstate, bus->activity, bus->idletime, bus->idlecount,
        bus->sleeping);
    dhd_dump_pct(strbuf, "Tx: glom pct", (0x64 * bus->txglompkts),
                 bus->dhd->tx_packets);
    dhd_dump_pct(strbuf, ", pkts/glom", bus->txglompkts, bus->txglomframes);
    bcm_bprintf(strbuf, "\n");
    bcm_bprintf(strbuf, "txglomframes %u, txglompkts %u\n", bus->txglomframes,
                bus->txglompkts);
    bcm_bprintf(strbuf, "\n");
}

void dhd_bus_clearcounts(dhd_pub_t *dhdp)
{
    dhd_bus_t *bus = (dhd_bus_t *)dhdp->bus;

    bus->intrcount = bus->lastintrs = bus->spurious = bus->regfails = 0;
    bus->rxrtx = bus->rx_toolong = bus->rxc_errors = 0;
    bus->rx_hdrfail = bus->rx_badhdr = bus->rx_badseq = 0;
#ifdef DHDENABLE_TAILPAD
    bus->tx_tailpad_chain = bus->tx_tailpad_pktget = 0;
#endif /* DHDENABLE_TAILPAD */
    bus->tx_sderrs = bus->fc_rcvd = bus->fc_xoff = bus->fc_xon = 0;
    bus->rxglomfail = bus->rxglomframes = bus->rxglompkts = 0;
    bus->f2rxhdrs = bus->f2rxdata = bus->f2txdata = bus->f1regdata = 0;
    bus->txglomframes = bus->txglompkts = 0;
}

#ifdef SDTEST
static int dhdsdio_pktgen_get(dhd_bus_t *bus, uint8 *arg)
{
    dhd_pktgen_t pktgen;

    pktgen.version = DHD_PKTGEN_VERSION;
    pktgen.freq = bus->pktgen_freq;
    pktgen.count = bus->pktgen_count;
    pktgen.print = bus->pktgen_print;
    pktgen.total = bus->pktgen_total;
    pktgen.minlen = bus->pktgen_minlen;
    pktgen.maxlen = bus->pktgen_maxlen;
    pktgen.numsent = bus->pktgen_sent;
    pktgen.numrcvd = bus->pktgen_rcvd;
    pktgen.numfail = bus->pktgen_fail;
    pktgen.mode = bus->pktgen_mode;
    pktgen.stop = bus->pktgen_stop;

    bcopy(&pktgen, arg, sizeof(pktgen));

    return 0;
}

static int dhdsdio_pktgen_set(dhd_bus_t *bus, uint8 *arg)
{
    dhd_pktgen_t pktgen;
    uint oldcnt, oldmode;

    bcopy(arg, &pktgen, sizeof(pktgen));
    if (pktgen.version != DHD_PKTGEN_VERSION) {
        return BCME_BADARG;
    }

    oldcnt = bus->pktgen_count;
    oldmode = bus->pktgen_mode;

    bus->pktgen_freq = pktgen.freq;
    bus->pktgen_count = pktgen.count;
    bus->pktgen_print = pktgen.print;
    bus->pktgen_total = pktgen.total;
    bus->pktgen_minlen = pktgen.minlen;
    bus->pktgen_maxlen = pktgen.maxlen;
    bus->pktgen_mode = pktgen.mode;
    bus->pktgen_stop = pktgen.stop;

    bus->pktgen_tick = bus->pktgen_ptick = 0;
    bus->pktgen_prev_time = jiffies;
    bus->pktgen_len = MAX(bus->pktgen_len, bus->pktgen_minlen);
    bus->pktgen_len = MIN(bus->pktgen_len, bus->pktgen_maxlen);

    /* Clear counts for a new pktgen (mode change, or was stopped) */
    if (bus->pktgen_count && (!oldcnt || oldmode != bus->pktgen_mode)) {
        bus->pktgen_sent = bus->pktgen_prev_sent = bus->pktgen_rcvd = 0;
        bus->pktgen_prev_rcvd = bus->pktgen_fail = 0;
    }

    return 0;
}
#endif /* SDTEST */

static void dhdsdio_devram_remap(dhd_bus_t *bus, bool val)
{
    uint8 enable, protect, remap;

    si_socdevram(bus->sih, FALSE, &enable, &protect, &remap);
    remap = val ? TRUE : FALSE;
    si_socdevram(bus->sih, TRUE, &enable, &protect, &remap);
}

static int dhdsdio_membytes(dhd_bus_t *bus, bool write, uint32 address,
                            uint8 *data, uint size)
{
    int bcmerror = 0;
    uint32 sdaddr;
    uint dsize;
    uint8 *pdata;

    /* In remap mode, adjust address beyond socram and redirect
     * to devram at SOCDEVRAM_BP_ADDR since remap address > orig_ramsize
     * is not backplane accessible
     */
    if (REMAP_ENAB(bus) && REMAP_ISADDR(bus, address)) {
        address -= bus->orig_ramsize;
        address += SOCDEVRAM_BP_ADDR;
    }

    /* Determine initial transfer parameters */
    sdaddr = address & SBSDIO_SB_OFT_ADDR_MASK;
    if ((sdaddr + size) & SBSDIO_SBWINDOW_MASK) {
        dsize = (SBSDIO_SB_OFT_ADDR_LIMIT - sdaddr);
    } else {
        dsize = size;
    }

    /* Set the backplane window to include the start address */
    if ((bcmerror = dhdsdio_set_siaddr_window(bus, address))) {
        DHD_ERROR(("%s: window change failed\n", __FUNCTION__));
        goto xfer_done;
    }

    /* Do the transfer(s) */
    while (size) {
        DHD_INFO(("%s: %s %d bytes at offset 0x%08x in window 0x%08x\n",
                  __FUNCTION__, (write ? "write" : "read"), dsize, sdaddr,
                  (address & SBSDIO_SBWINDOW_MASK)));
        if (dsize <= MAX_MEM_BUF) {
            pdata = bus->membuf;
            if (write) {
                memcpy(bus->membuf, data, dsize);
            }
        } else {
            pdata = data;
        }
        if ((bcmerror = bcmsdh_rwdata(bus->sdh, write, sdaddr, pdata, dsize))) {
            DHD_ERROR(("%s: membytes transfer failed\n", __FUNCTION__));
            break;
        }
        if (dsize <= MAX_MEM_BUF && !write) {
            memcpy(data, bus->membuf, dsize);
        }

        /* Adjust for next transfer (if any) */
        if ((size -= dsize)) {
            data += dsize;
            address += dsize;
            if ((bcmerror = dhdsdio_set_siaddr_window(bus, address))) {
                DHD_ERROR(("%s: window change failed\n", __FUNCTION__));
                break;
            }
            sdaddr = 0;
            dsize = MIN(SBSDIO_SB_OFT_ADDR_LIMIT, size);
        }
    }

xfer_done:
    /* Return the window to backplane enumeration space for core access */
    if (dhdsdio_set_siaddr_window(bus, bcmsdh_cur_sbwad(bus->sdh))) {
        DHD_ERROR(("%s: FAILED to set window back to 0x%x\n", __FUNCTION__,
                   bcmsdh_cur_sbwad(bus->sdh)));
    }

    return bcmerror;
}

static int dhdsdio_readshared(dhd_bus_t *bus, sdpcm_shared_t *sh)
{
    uint32 addr;
    int rv, i;
    uint32 shaddr = 0;

    if (bus->sih == NULL) {
        if (bus->dhd && bus->dhd->dongle_reset) {
            DHD_ERROR(("%s: Dongle is in reset state\n", __FUNCTION__));
            return BCME_NOTREADY;
        } else {
            ASSERT(bus->dhd);
            ASSERT(bus->sih);
            DHD_ERROR(("%s: The address of sih is invalid\n", __FUNCTION__));
            return BCME_ERROR;
        }
    }
    if ((CHIPID(bus->sih->chip) == BCM43430_CHIP_ID ||
         CHIPID(bus->sih->chip) == BCM43018_CHIP_ID) &&
        !dhdsdio_sr_cap(bus)) {
        bus->srmemsize = 0;
    }

    shaddr = bus->dongle_ram_base + bus->ramsize - 0x4;
    i = 0;
    do {
        /* Read last word in memory to determine address of sdpcm_shared
         * structure */
        if ((rv = dhdsdio_membytes(bus, FALSE, shaddr, (uint8 *)&addr, 0x4)) <
            0) {
            return rv;
        }

        addr = ltoh32(addr);

        DHD_INFO(("sdpcm_shared address 0x%08X\n", addr));

        /*
         * Check if addr is valid.
         * NVRAM length at the end of memory should have been overwritten.
         */
        if (addr == 0 || ((~addr >> 0x10) & 0xffff) == (addr & 0xffff)) {
            if ((bus->srmemsize > 0) && (i++ == 0)) {
                shaddr -= bus->srmemsize;
            } else {
                DHD_ERROR(("%s: address (0x%08x) of sdpcm_shared invalid\n",
                           __FUNCTION__, addr));
                return BCME_ERROR;
            }
        } else {
            break;
        }
    } while (i < 0x2);

    /* Read hndrte_shared structure */
    if ((rv = dhdsdio_membytes(bus, FALSE, addr, (uint8 *)sh,
                               sizeof(sdpcm_shared_t))) < 0) {
        return rv;
    }

    /* Endianness */
    sh->flags = ltoh32(sh->flags);
    sh->trap_addr = ltoh32(sh->trap_addr);
    sh->assert_exp_addr = ltoh32(sh->assert_exp_addr);
    sh->assert_file_addr = ltoh32(sh->assert_file_addr);
    sh->assert_line = ltoh32(sh->assert_line);
    sh->console_addr = ltoh32(sh->console_addr);
    sh->msgtrace_addr = ltoh32(sh->msgtrace_addr);

#ifdef BCMSDIO_RXLIM_POST
    if (sh->flags & SDPCM_SHARED_RXLIM_POST) {
        if (bus->dhd->conf->rxlim_en) {
            bus->rxlim_en = !!sh->msgtrace_addr;
        }
        bus->rxlim_addr = sh->msgtrace_addr;
        DHD_INFO(("%s: rxlim_en=%d, rxlim enable=%d, rxlim_addr=%d\n",
                  __FUNCTION__, bus->dhd->conf->rxlim_en, bus->rxlim_en,
                  bus->rxlim_addr));
        sh->flags &= ~SDPCM_SHARED_RXLIM_POST;
    } else {
        bus->rxlim_en = 0;
        DHD_INFO(("%s: FW has no rx limit post support\n", __FUNCTION__));
    }
#endif /* BCMSDIO_RXLIM_POST */

#ifdef BCMSDIO_TXSEQ_SYNC
    if (bus->dhd->conf->txseq_sync) {
        sh->txseq_sync_addr = ltoh32(sh->txseq_sync_addr);
        if (sh->flags & SDPCM_SHARED_TXSEQ_SYNC) {
            uint8 val = 0;
            DHD_INFO(("%s: TXSEQ_SYNC enabled in fw\n", __FUNCTION__));
            if (0 == dhdsdio_membytes(bus, FALSE, sh->txseq_sync_addr,
                                      (uint8 *)&val, 1)) {
                if (bus->tx_seq != val) {
                    DHD_INFO(("%s: Sync tx_seq from %d to %d\n", __FUNCTION__,
                              bus->tx_seq, val));
                    bus->tx_seq = val;
                    bus->tx_max = bus->tx_seq + 0x4;
                }
            }
            sh->flags &= ~SDPCM_SHARED_TXSEQ_SYNC;
        } else {
            bus->dhd->conf->txseq_sync = FALSE;
        }
    }
#endif /* BCMSDIO_TXSEQ_SYNC */

    if ((sh->flags & SDPCM_SHARED_VERSION_MASK) == 0x3 &&
        SDPCM_SHARED_VERSION == 1) {
        return BCME_OK;
    }

    if ((sh->flags & SDPCM_SHARED_VERSION_MASK) != SDPCM_SHARED_VERSION) {
        DHD_ERROR(("%s: sdpcm_shared version %d in dhd "
                   "is different than sdpcm_shared version %d in dongle\n",
                   __FUNCTION__, SDPCM_SHARED_VERSION,
                   sh->flags & SDPCM_SHARED_VERSION_MASK));
        return BCME_ERROR;
    }

    return BCME_OK;
}

#define CONSOLE_LINE_MAX 192

#ifdef DHD_DEBUG
static int dhdsdio_readconsole(dhd_bus_t *bus)
{
    dhd_console_t *c = &bus->console;
    uint8 line[CONSOLE_LINE_MAX], ch;
    uint32 n, idx, addr;
    int rv;

    /* Don't do anything until FWREADY updates console address */
    if (bus->console_addr == 0) {
        return 0;
    }

    if (!KSO_ENAB(bus)) {
        return 0;
    }

    /* Read console log struct */
    addr = bus->console_addr + OFFSETOF(hnd_cons_t, log);
    if ((rv = dhdsdio_membytes(bus, FALSE, addr, (uint8 *)&c->log,
                               sizeof(c->log))) < 0) {
        return rv;
    }

    /* Allocate console buffer (one time only) */
    if (c->buf == NULL) {
        c->bufsize = ltoh32(c->log.buf_size);
        if ((c->buf = MALLOC(bus->dhd->osh, c->bufsize)) == NULL) {
            return BCME_NOMEM;
        }
    }

    idx = ltoh32(c->log.idx);
    /* Protect against corrupt value */
    if (idx > c->bufsize) {
        return BCME_ERROR;
    }
    /* Skip reading the console buffer if the index pointer has not moved */
    if (idx == c->last) {
        return BCME_OK;
    }

    /* Read the console buffer */
    addr = ltoh32(c->log.buf);
    if ((rv = dhdsdio_membytes(bus, FALSE, addr, c->buf, c->bufsize)) < 0) {
        return rv;
    }

    while (c->last != idx) {
        for (n = 0; n < CONSOLE_LINE_MAX - 0x2; n++) {
            if (c->last == idx) {
                /* This would output a partial line.  Instead, back up
                 * the buffer pointer and output this line next time around.
                 */
                if (c->last >= n) {
                    c->last -= n;
                } else {
                    c->last = c->bufsize - n;
                }
                goto break2;
            }
            ch = c->buf[c->last];
            c->last = (c->last + 1) % c->bufsize;
            if (ch == '\n') {
                break;
            }
            line[n] = ch;
        }

        if (n > 0) {
            if (line[n - 1] == '\r') {
                n--;
            }
            line[n] = 0;
            printf("CONSOLE: %s\n", line);
#ifdef LOG_INTO_TCPDUMP
            dhd_sendup_log(bus->dhd, line, n);
#endif /* LOG_INTO_TCPDUMP */
        }
    }
break2:

    return BCME_OK;
}
#endif /* DHD_DEBUG */

static int dhdsdio_checkdied(dhd_bus_t *bus, char *data, uint size)
{
    int bcmerror = 0;
    uint msize = 512;
    char *mbuffer = NULL;
    char *console_buffer = NULL;
    uint maxstrlen = 256;
    char *str = NULL;
    sdpcm_shared_t l_sdpcm_shared;
    struct bcmstrbuf strbuf;
    uint32 console_ptr, console_size, console_index;
    uint8 line[CONSOLE_LINE_MAX], ch;
    uint32 n, i, addr;
    int rv;

    DHD_TRACE(("%s: Enter\n", __FUNCTION__));

    if (DHD_NOCHECKDIED_ON()) {
        return 0;
    }

    if (data == NULL) {
        /*
         * Called after a rx ctrl timeout. "data" is NULL.
         * allocate memory to trace the trap or assert.
         */
        size = msize;
        mbuffer = data = MALLOC(bus->dhd->osh, msize);
        if (mbuffer == NULL) {
            DHD_ERROR(("%s: MALLOC(%d) failed \n", __FUNCTION__, msize));
            bcmerror = BCME_NOMEM;
            goto done;
        }
    }

    if ((str = MALLOC(bus->dhd->osh, maxstrlen)) == NULL) {
        DHD_ERROR(("%s: MALLOC(%d) failed \n", __FUNCTION__, maxstrlen));
        bcmerror = BCME_NOMEM;
        goto done;
    }

    if ((bcmerror = dhdsdio_readshared(bus, &l_sdpcm_shared)) < 0) {
        goto done;
    }

    bcm_binit(&strbuf, data, size);

    bcm_bprintf(&strbuf,
                "msgtrace address : 0x%08X\nconsole address  : 0x%08X\n",
                l_sdpcm_shared.msgtrace_addr, l_sdpcm_shared.console_addr);

    if ((l_sdpcm_shared.flags & SDPCM_SHARED_ASSERT_BUILT) == 0) {
        /* NOTE: Misspelled assert is intentional - DO NOT FIX.
         * (Avoids conflict with real asserts for programmatic parsing of
         * output.)
         */
        bcm_bprintf(&strbuf, "Assrt not built in dongle\n");
    }

    if ((l_sdpcm_shared.flags & (SDPCM_SHARED_ASSERT | SDPCM_SHARED_TRAP)) ==
        0) {
        /* NOTE: Misspelled assert is intentional - DO NOT FIX.
         * (Avoids conflict with real asserts for programmatic parsing of
         * output.)
         */
        bcm_bprintf(
            &strbuf, "No trap%s in dongle",
            (l_sdpcm_shared.flags & SDPCM_SHARED_ASSERT_BUILT) ? "/assrt" : "");
    } else {
        if (l_sdpcm_shared.flags & SDPCM_SHARED_ASSERT) {
            /* Download assert */
            bcm_bprintf(&strbuf, "Dongle assert");
            if (l_sdpcm_shared.assert_exp_addr != 0) {
                str[0] = '\0';
                if ((bcmerror = dhdsdio_membytes(
                         bus, FALSE, l_sdpcm_shared.assert_exp_addr,
                         (uint8 *)str, maxstrlen)) < 0) {
                    goto done;
                }

                str[maxstrlen - 1] = '\0';
                bcm_bprintf(&strbuf, " expr \"%s\"", str);
            }

            if (l_sdpcm_shared.assert_file_addr != 0) {
                str[0] = '\0';
                if ((bcmerror = dhdsdio_membytes(
                         bus, FALSE, l_sdpcm_shared.assert_file_addr,
                         (uint8 *)str, maxstrlen)) < 0) {
                    goto done;
                }

                str[maxstrlen - 1] = '\0';
                bcm_bprintf(&strbuf, " file \"%s\"", str);
            }

            bcm_bprintf(&strbuf, " line %d ", l_sdpcm_shared.assert_line);
        }

        if (l_sdpcm_shared.flags & SDPCM_SHARED_TRAP) {
            trap_t *tr = &bus->dhd->last_trap_info;
            bus->dhd->dongle_trap_occured = TRUE;
            if ((bcmerror =
                     dhdsdio_membytes(bus, FALSE, l_sdpcm_shared.trap_addr,
                                      (uint8 *)tr, sizeof(trap_t))) < 0) {
                goto done;
            }

            bus->dongle_trap_addr = ltoh32(l_sdpcm_shared.trap_addr);

            dhd_bus_dump_trap_info(bus, &strbuf);

            addr = l_sdpcm_shared.console_addr + OFFSETOF(hnd_cons_t, log);
            if ((rv = dhdsdio_membytes(bus, FALSE, addr, (uint8 *)&console_ptr,
                                       sizeof(console_ptr))) < 0) {
                goto printbuf;
            }

            addr = l_sdpcm_shared.console_addr +
                   OFFSETOF(hnd_cons_t, log.buf_size);
            if ((rv = dhdsdio_membytes(bus, FALSE, addr, (uint8 *)&console_size,
                                       sizeof(console_size))) < 0) {
                goto printbuf;
            }

            addr = l_sdpcm_shared.console_addr + OFFSETOF(hnd_cons_t, log.idx);
            if ((rv =
                     dhdsdio_membytes(bus, FALSE, addr, (uint8 *)&console_index,
                                      sizeof(console_index))) < 0) {
                goto printbuf;
            }

            console_ptr = ltoh32(console_ptr);
            console_size = ltoh32(console_size);
            console_index = ltoh32(console_index);

            if (console_size > CONSOLE_BUFFER_MAX ||
                !(console_buffer = MALLOC(bus->dhd->osh, console_size))) {
                goto printbuf;
            }

            if ((rv = dhdsdio_membytes(bus, FALSE, console_ptr,
                                       (uint8 *)console_buffer, console_size)) <
                0) {
                goto printbuf;
            }

            for (i = 0, n = 0; i < console_size; i += n + 1) {
                for (n = 0; n < CONSOLE_LINE_MAX - 0x2; n++) {
                    ch = console_buffer[(console_index + i + n) % console_size];
                    if (ch == '\n') {
                        break;
                    }
                    line[n] = ch;
                }

                if (n > 0) {
                    if (line[n - 1] == '\r') {
                        n--;
                    }
                    line[n] = 0;
                    /* Don't use DHD_ERROR macro since we print
                     * a lot of information quickly. The macro
                     * will truncate a lot of the printfs
                     */

                    if (dhd_msg_level & DHD_ERROR_VAL) {
                        printf("CONSOLE: %s\n", line);
                    }
                }
            }
        }
    }

printbuf:
    if (l_sdpcm_shared.flags & (SDPCM_SHARED_ASSERT | SDPCM_SHARED_TRAP)) {
        DHD_ERROR(("%s: %s\n", __FUNCTION__, strbuf.origbuf));
    }

#if defined(DHD_FW_COREDUMP)
    if (bus->dhd->memdump_enabled &&
        (l_sdpcm_shared.flags & SDPCM_SHARED_TRAP)) {
        /* Mem dump to a file on device */
        bus->dhd->memdump_type = DUMP_TYPE_DONGLE_TRAP;
        dhd_os_sdunlock(bus->dhd);
        dhdsdio_mem_dump(bus);
        dhd_os_sdlock(bus->dhd);
    }
#endif /* #if defined(DHD_FW_COREDUMP) */

done:
    if (mbuffer) {
        MFREE(bus->dhd->osh, mbuffer, msize);
    }
    if (str) {
        MFREE(bus->dhd->osh, str, maxstrlen);
    }
    if (console_buffer) {
        MFREE(bus->dhd->osh, console_buffer, console_size);
    }

    return bcmerror;
}

#if defined(DHD_FW_COREDUMP)
int dhd_bus_mem_dump(dhd_pub_t *dhdp)
{
    dhd_bus_t *bus = dhdp->bus;
    if (dhdp->busstate == DHD_BUS_SUSPEND) {
        DHD_ERROR(("%s: Bus is suspend so skip\n", __FUNCTION__));
        return 0;
    }
    return dhdsdio_mem_dump(bus);
}

int dhd_bus_get_mem_dump(dhd_pub_t *dhdp)
{
    if (!dhdp) {
        DHD_ERROR(("%s: dhdp is NULL\n", __FUNCTION__));
        return BCME_ERROR;
    }

    return dhdsdio_get_mem_dump(dhdp->bus);
}

static int dhdsdio_get_mem_dump(dhd_bus_t *bus)
{
    int ret = BCME_ERROR;
    int size = bus->ramsize;             /* Full mem size */
    uint32 start = bus->dongle_ram_base; /* Start address */
    uint read_size = 0;                  /* Read size of each iteration */
    uint8 *p_buf = NULL, *databuf = NULL;

    /* Get full mem size */
    p_buf = dhd_get_fwdump_buf(bus->dhd, size);
    if (!p_buf) {
        DHD_ERROR(("%s: Out of memory (%d bytes)\n", __FUNCTION__, size));
        return BCME_ERROR;
    }

    dhd_os_sdlock(bus->dhd);
    BUS_WAKE(bus);
    dhdsdio_clkctl(bus, CLK_AVAIL, FALSE);

    /* Read mem content */
    DHD_ERROR(("Dump dongle memory\n"));
    databuf = p_buf;
    while (size) {
        read_size = MIN(MEMBLOCK, size);
        ret = dhdsdio_membytes(bus, FALSE, start, databuf, read_size);
        if (ret) {
            DHD_ERROR(("%s: Error membytes %d\n", __FUNCTION__, ret));
            ret = BCME_ERROR;
            break;
        }
        /* Decrement size and increment start address */
        size -= read_size;
        start += read_size;
        databuf += read_size;
    }

    if ((bus->idletime == DHD_IDLE_IMMEDIATE) && !bus->dpc_sched &&
        NO_OTHER_ACTIVE_BUS_USER(bus)) {
        bus->activity = FALSE;
        dhdsdio_clkctl(bus, CLK_NONE, TRUE);
    }

    dhd_os_sdunlock(bus->dhd);

    return ret;
}

static int dhdsdio_mem_dump(dhd_bus_t *bus)
{
    dhd_pub_t *dhdp;
    int ret = BCME_ERROR;

    dhdp = bus->dhd;
    if (!dhdp) {
        DHD_ERROR(("%s: dhdp is NULL\n", __FUNCTION__));
        return ret;
    }

    ret = dhdsdio_get_mem_dump(bus);
    if (ret) {
        DHD_ERROR(("%s: failed to get mem dump, err=%d\n", __FUNCTION__, ret));
    } else {
        /* schedule a work queue to perform actual memdump.
         * dhd_mem_dump() performs the job
         */
        dhd_schedule_memdump(dhdp, dhdp->soc_ram, dhdp->soc_ram_length);
        /* soc_ram free handled in dhd_{free,clear} */
    }

    return ret;
}
#endif /* DHD_FW_COREDUMP */

int dhd_socram_dump(dhd_bus_t *bus)
{
#if defined(DHD_FW_COREDUMP)
    return (dhdsdio_mem_dump(bus));
#else
    return -1;
#endif // endif
}

int dhdsdio_downloadvars(dhd_bus_t *bus, void *arg, int len)
{
    int bcmerror = BCME_OK;

    DHD_TRACE(("%s: Enter\n", __FUNCTION__));

    if (bus->dhd->up &&
#ifdef DHD_ULP
        (DHD_ULP_DISABLED == dhd_ulp_get_ulp_state(bus->dhd)) &&
#endif /* DHD_ULP */
        1) {
        bcmerror = BCME_NOTDOWN;
        goto err;
    }
    if (!len) {
        bcmerror = BCME_BUFTOOSHORT;
        goto err;
    }

    /* Free the old ones and replace with passed variables */
    if (bus->vars) {
        MFREE(bus->dhd->osh, bus->vars, bus->varsz);
    }

    bus->vars = MALLOC(bus->dhd->osh, len);
    bus->varsz = bus->vars ? len : 0;
    if (bus->vars == NULL) {
        bcmerror = BCME_NOMEM;
        goto err;
    }

    /* Copy the passed variables, which should include the terminating
     * double-null */
    bcopy(arg, bus->vars, bus->varsz);
err:
    return bcmerror;
}

#ifdef DHD_DEBUG
static int dhd_serialconsole(dhd_bus_t *bus, bool set, bool enable,
                             int *bcmerror)
{
    int int_val;
    uint32 addr, data, uart_enab = 0;

    addr = SI_ENUM_BASE(bus->sih) + OFFSETOF(chipcregs_t, chipcontrol_addr);
    data = SI_ENUM_BASE(bus->sih) + OFFSETOF(chipcregs_t, chipcontrol_data);
    *bcmerror = 0;

    bcmsdh_reg_write(bus->sdh, addr, 0x4, 1);
    if (bcmsdh_regfail(bus->sdh)) {
        *bcmerror = BCME_SDIO_ERROR;
        return -1;
    }
    int_val = bcmsdh_reg_read(bus->sdh, data, 0x4);
    if (bcmsdh_regfail(bus->sdh)) {
        *bcmerror = BCME_SDIO_ERROR;
        return -1;
    }

    if (!set) {
        return (int_val & uart_enab);
    }
    if (enable) {
        int_val |= uart_enab;
    } else {
        int_val &= ~uart_enab;
    }
    bcmsdh_reg_write(bus->sdh, data, 0x4, int_val);
    if (bcmsdh_regfail(bus->sdh)) {
        *bcmerror = BCME_SDIO_ERROR;
        return -1;
    }

    return (int_val & uart_enab);
}
#endif // endif

static int dhdsdio_doiovar(dhd_bus_t *bus, const bcm_iovar_t *vi,
                           uint32 actionid, const char *name, void *params,
                           int plen, void *arg, int len, int val_size)
{
    int bcmerror = 0;
    int32 int_val = 0;
    bool bool_val = 0;

    DHD_TRACE(("%s: Enter, action %d name %s params %p plen %d arg %p len %d "
               "val_size %d\n",
               __FUNCTION__, actionid, name, params, plen, arg, len, val_size));

    if ((bcmerror = bcm_iovar_lencheck(vi, arg, len, IOV_ISSET(actionid))) !=
        0) {
        goto exit;
    }

    if (plen >= (int)sizeof(int_val)) {
        bcopy(params, &int_val, sizeof(int_val));
    }

    bool_val = (int_val != 0) ? TRUE : FALSE;

    /* Some ioctls use the bus */
    dhd_os_sdlock(bus->dhd);

    /* Check if dongle is in reset. If so, only allow DEVRESET iovars */
    if (bus->dhd->dongle_reset && !(actionid == IOV_SVAL(IOV_DEVRESET) ||
                                    actionid == IOV_GVAL(IOV_DEVRESET))) {
        bcmerror = BCME_NOTREADY;
        goto exit;
    }

    /*
     * Special handling for keepSdioOn: New SDIO Wake-up Mechanism
     */
    if ((vi->varid == IOV_KSO) && (IOV_ISSET(actionid))) {
        dhdsdio_clk_kso_iovar(bus, bool_val);
        goto exit;
    } else if ((vi->varid == IOV_DEVSLEEP) && (IOV_ISSET(actionid))) {
        {
            dhdsdio_clk_devsleep_iovar(bus, bool_val);
            if (!SLPAUTO_ENAB(bus) && (bool_val == FALSE) && (bus->ipend)) {
                DHD_ERROR(("INT pending in devsleep 1, dpc_sched: %d\n",
                           bus->dpc_sched));
                if (!bus->dpc_sched) {
                    bus->dpc_sched = TRUE;
                    dhd_sched_dpc(bus->dhd);
                }
            }
        }
        goto exit;
    }

    /* Handle sleep stuff before any clock mucking */
    if (vi->varid == IOV_SLEEP) {
        if (IOV_ISSET(actionid)) {
            bcmerror = dhdsdio_bussleep(bus, bool_val);
        } else {
            int_val = (int32)bus->sleeping;
            bcopy(&int_val, arg, val_size);
        }
        goto exit;
    }

    /* Request clock to allow SDIO accesses */
    if (!bus->dhd->dongle_reset) {
        BUS_WAKE(bus);
        dhdsdio_clkctl(bus, CLK_AVAIL, FALSE);
    }

    switch (actionid) {
        case IOV_GVAL(IOV_INTR):
            int_val = (int32)bus->intr;
            bcopy(&int_val, arg, val_size);
            break;

        case IOV_SVAL(IOV_INTR):
            bus->intr = bool_val;
            bus->intdis = FALSE;
            if (bus->dhd->up) {
                if (bus->intr) {
                    DHD_INTR(
                        ("%s: enable SDIO device interrupts\n", __FUNCTION__));
                    // terence 20141207: enbale intdis
                    bus->intdis = TRUE;
                    bcmsdh_intr_enable(bus->sdh);
                } else {
                    DHD_INTR(("%s: disable SDIO interrupts\n", __FUNCTION__));
                    bcmsdh_intr_disable(bus->sdh);
                }
            }
            break;

        case IOV_GVAL(IOV_POLLRATE):
            int_val = (int32)bus->pollrate;
            bcopy(&int_val, arg, val_size);
            break;

        case IOV_SVAL(IOV_POLLRATE):
            bus->pollrate = (uint)int_val;
            bus->poll = (bus->pollrate != 0);
            break;

        case IOV_GVAL(IOV_IDLETIME):
            int_val = bus->idletime;
            bcopy(&int_val, arg, val_size);
            break;

        case IOV_SVAL(IOV_IDLETIME):
            if ((int_val < 0) && (int_val != DHD_IDLE_IMMEDIATE)) {
                bcmerror = BCME_BADARG;
            } else {
                bus->idletime = int_val;
            }
            break;

        case IOV_GVAL(IOV_IDLECLOCK):
            int_val = (int32)bus->idleclock;
            bcopy(&int_val, arg, val_size);
            break;

        case IOV_SVAL(IOV_IDLECLOCK):
            bus->idleclock = int_val;
            break;

        case IOV_GVAL(IOV_SD1IDLE):
            int_val = (int32)sd1idle;
            bcopy(&int_val, arg, val_size);
            break;

        case IOV_SVAL(IOV_SD1IDLE):
            sd1idle = bool_val;
            break;

#ifdef DHD_DEBUG
        case IOV_GVAL(IOV_CHECKDIED):
            bcmerror = dhdsdio_checkdied(bus, arg, len);
            break;
#endif /* DHD_DEBUG */

        case IOV_GVAL(IOV_RAMSIZE):
            int_val = (int32)bus->ramsize;
            bcopy(&int_val, arg, val_size);
            break;

        case IOV_GVAL(IOV_RAMSTART):
            int_val = (int32)bus->dongle_ram_base;
            bcopy(&int_val, arg, val_size);
            break;

        case IOV_GVAL(IOV_SDIOD_DRIVE):
            int_val = (int32)dhd_sdiod_drive_strength;
            bcopy(&int_val, arg, val_size);
            break;

        case IOV_SVAL(IOV_SDIOD_DRIVE):
            dhd_sdiod_drive_strength = int_val;
            si_sdiod_drive_strength_init(bus->sih, bus->dhd->osh,
                                         dhd_sdiod_drive_strength);
            break;

        case IOV_SVAL(IOV_SET_DOWNLOAD_STATE):
            bcmerror = dhdsdio_download_state(bus, bool_val);
            break;

        case IOV_SVAL(IOV_SOCRAM_STATE):
            bcmerror = dhdsdio_download_state(bus, bool_val);
            break;

        case IOV_SVAL(IOV_VARS):
            bcmerror = dhdsdio_downloadvars(bus, arg, len);
            break;

        case IOV_GVAL(IOV_READAHEAD):
            int_val = (int32)dhd_readahead;
            bcopy(&int_val, arg, val_size);
            break;

        case IOV_SVAL(IOV_READAHEAD):
            if (bool_val && !dhd_readahead) {
                bus->nextlen = 0;
            }
            dhd_readahead = bool_val;
            break;

        case IOV_GVAL(IOV_SDRXCHAIN):
            int_val = (int32)bus->use_rxchain;
            bcopy(&int_val, arg, val_size);
            break;

        case IOV_SVAL(IOV_SDRXCHAIN):
            if (bool_val && !bus->sd_rxchain) {
                bcmerror = BCME_UNSUPPORTED;
            } else {
                bus->use_rxchain = bool_val;
            }
            break;
#ifndef BCMSPI
        case IOV_GVAL(IOV_ALIGNCTL):
            int_val = (int32)dhd_alignctl;
            bcopy(&int_val, arg, val_size);
            break;

        case IOV_SVAL(IOV_ALIGNCTL):
            dhd_alignctl = bool_val;
            break;
#endif /* BCMSPI */

        case IOV_GVAL(IOV_SDALIGN):
            int_val = DHD_SDALIGN;
            bcopy(&int_val, arg, val_size);
            break;

#ifdef DHD_DEBUG
        case IOV_GVAL(IOV_VARS):
            if (bus->varsz < (uint)len) {
                bcopy(bus->vars, arg, bus->varsz);
            } else {
                bcmerror = BCME_BUFTOOSHORT;
            }
            break;
#endif /* DHD_DEBUG */

#ifdef DHD_DEBUG
        case IOV_GVAL(IOV_SDREG): {
            sdreg_t *sd_ptr;
            uintptr addr;
            uint size;

            sd_ptr = (sdreg_t *)params;

            addr = ((uintptr)bus->regs + sd_ptr->offset);
            size = sd_ptr->func;
            int_val = (int32)bcmsdh_reg_read(bus->sdh, addr, size);
            if (bcmsdh_regfail(bus->sdh)) {
                bcmerror = BCME_SDIO_ERROR;
            }
            bcopy(&int_val, arg, sizeof(int32));
            break;
        }

        case IOV_SVAL(IOV_SDREG): {
            sdreg_t *sd_ptr;
            uintptr addr;
            uint size;

            sd_ptr = (sdreg_t *)params;

            addr = ((uintptr)bus->regs + sd_ptr->offset);
            size = sd_ptr->func;
            bcmsdh_reg_write(bus->sdh, addr, size, sd_ptr->value);
            if (bcmsdh_regfail(bus->sdh)) {
                bcmerror = BCME_SDIO_ERROR;
            }
            break;
        }

        /* Same as above, but offset is not backplane (not SDIO core) */
        case IOV_GVAL(IOV_SBREG): {
            sdreg_t sdreg;
            uint32 addr, size;

            bcopy(params, &sdreg, sizeof(sdreg));

            addr = SI_ENUM_BASE(bus->sih) + sdreg.offset;
            size = sdreg.func;
            int_val = (int32)bcmsdh_reg_read(bus->sdh, addr, size);
            if (bcmsdh_regfail(bus->sdh)) {
                bcmerror = BCME_SDIO_ERROR;
            }
            bcopy(&int_val, arg, sizeof(int32));
            break;
        }

        case IOV_SVAL(IOV_SBREG): {
            sdreg_t sdreg;
            uint32 addr, size;

            bcopy(params, &sdreg, sizeof(sdreg));

            addr = SI_ENUM_BASE(bus->sih) + sdreg.offset;
            size = sdreg.func;
            bcmsdh_reg_write(bus->sdh, addr, size, sdreg.value);
            if (bcmsdh_regfail(bus->sdh)) {
                bcmerror = BCME_SDIO_ERROR;
            }
            break;
        }

        case IOV_GVAL(IOV_SDCIS): {
            *(char *)arg = 0;

            bcmstrcat(arg, "\nFunc 0\n");
            bcmsdh_cis_read(bus->sdh, 0x10, (uint8 *)arg + strlen(arg),
                            SBSDIO_CIS_SIZE_LIMIT);
            bcmstrcat(arg, "\nFunc 1\n");
            bcmsdh_cis_read(bus->sdh, 0x11, (uint8 *)arg + strlen(arg),
                            SBSDIO_CIS_SIZE_LIMIT);
            bcmstrcat(arg, "\nFunc 2\n");
            bcmsdh_cis_read(bus->sdh, 0x12, (uint8 *)arg + strlen(arg),
                            SBSDIO_CIS_SIZE_LIMIT);
            break;
        }

        case IOV_GVAL(IOV_FORCEEVEN):
            int_val = (int32)forcealign;
            bcopy(&int_val, arg, val_size);
            break;

        case IOV_SVAL(IOV_FORCEEVEN):
            forcealign = bool_val;
            break;

        case IOV_GVAL(IOV_TXBOUND):
            int_val = (int32)dhd_txbound;
            bcopy(&int_val, arg, val_size);
            break;

        case IOV_SVAL(IOV_TXBOUND):
            dhd_txbound = (uint)int_val;
            break;

        case IOV_GVAL(IOV_RXBOUND):
            int_val = (int32)dhd_rxbound;
            bcopy(&int_val, arg, val_size);
            break;

        case IOV_SVAL(IOV_RXBOUND):
            dhd_rxbound = (uint)int_val;
            break;

        case IOV_GVAL(IOV_TXMINMAX):
            int_val = (int32)dhd_txminmax;
            bcopy(&int_val, arg, val_size);
            break;

        case IOV_SVAL(IOV_TXMINMAX):
            dhd_txminmax = (uint)int_val;
            break;

#ifdef DHD_DEBUG
        case IOV_GVAL(IOV_SERIALCONS):
            int_val = dhd_serialconsole(bus, FALSE, 0, &bcmerror);
            if (bcmerror != 0) {
                break;
            }

            bcopy(&int_val, arg, val_size);
            break;

        case IOV_SVAL(IOV_SERIALCONS):
            dhd_serialconsole(bus, TRUE, bool_val, &bcmerror);
            break;
#endif /* DHD_DEBUG */

#endif /* DHD_DEBUG */

#ifdef SDTEST
        case IOV_GVAL(IOV_EXTLOOP):
            int_val = (int32)bus->ext_loop;
            bcopy(&int_val, arg, val_size);
            break;

        case IOV_SVAL(IOV_EXTLOOP):
            bus->ext_loop = bool_val;
            break;

        case IOV_GVAL(IOV_PKTGEN):
            bcmerror = dhdsdio_pktgen_get(bus, arg);
            break;

        case IOV_SVAL(IOV_PKTGEN):
            bcmerror = dhdsdio_pktgen_set(bus, arg);
            break;
#endif /* SDTEST */

#if defined(USE_SDIOFIFO_IOVAR)
        case IOV_GVAL(IOV_WATERMARK):
            int_val = (int32)watermark;
            bcopy(&int_val, arg, val_size);
            break;

        case IOV_SVAL(IOV_WATERMARK):
            watermark = (uint)int_val;
            watermark = (watermark > SBSDIO_WATERMARK_MASK)
                            ? SBSDIO_WATERMARK_MASK
                            : watermark;
            DHD_ERROR(("Setting watermark as 0x%x.\n", watermark));
            bcmsdh_cfg_write(bus->sdh, SDIO_FUNC_1, SBSDIO_WATERMARK,
                             (uint8)watermark, NULL);
            break;

        case IOV_GVAL(IOV_MESBUSYCTRL):
            int_val = (int32)mesbusyctrl;
            bcopy(&int_val, arg, val_size);
            break;

        case IOV_SVAL(IOV_MESBUSYCTRL):
            mesbusyctrl = (uint)int_val;
            mesbusyctrl = (mesbusyctrl > SBSDIO_MESBUSYCTRL_MASK)
                              ? SBSDIO_MESBUSYCTRL_MASK
                              : mesbusyctrl;
            DHD_ERROR(("Setting mesbusyctrl as 0x%x.\n", mesbusyctrl));
            bcmsdh_cfg_write(bus->sdh, SDIO_FUNC_1, SBSDIO_FUNC1_MESBUSYCTRL,
                             ((uint8)mesbusyctrl | 0x80), NULL);
            break;
#endif // endif

        case IOV_GVAL(IOV_DONGLEISOLATION):
            int_val = bus->dhd->dongle_isolation;
            bcopy(&int_val, arg, val_size);
            break;

        case IOV_SVAL(IOV_DONGLEISOLATION):
            bus->dhd->dongle_isolation = bool_val;
            break;

        case IOV_SVAL(IOV_DEVRESET):
            DHD_TRACE(
                ("%s: Called set IOV_DEVRESET=%d dongle_reset=%d busstate=%d\n",
                 __FUNCTION__, bool_val, bus->dhd->dongle_reset,
                 bus->dhd->busstate));

            ASSERT(bus->dhd->osh);

            /* must release sdlock, since devreset also acquires it */
            dhd_os_sdunlock(bus->dhd);
            dhd_bus_devreset(bus->dhd, (uint8)bool_val);
            dhd_os_sdlock(bus->dhd);
            break;
            /*
             * softap firmware is updated through module parameter or ohos
             * private command
             */

        case IOV_GVAL(IOV_DEVRESET):
            DHD_TRACE(("%s: Called get IOV_DEVRESET\n", __FUNCTION__));

            /* Get its status */
            int_val = (bool)bus->dhd->dongle_reset;
            bcopy(&int_val, arg, val_size);

            break;

        case IOV_GVAL(IOV_KSO):
            int_val = dhdsdio_sleepcsr_get(bus);
            bcopy(&int_val, arg, val_size);
            break;

        case IOV_GVAL(IOV_DEVCAP):
            int_val = dhdsdio_devcap_get(bus);
            bcopy(&int_val, arg, val_size);
            break;

        case IOV_SVAL(IOV_DEVCAP):
            dhdsdio_devcap_set(bus, (uint8)int_val);
            break;
        case IOV_GVAL(IOV_TXGLOMSIZE):
            int_val = (int32)bus->txglomsize;
            bcopy(&int_val, arg, val_size);
            break;

        case IOV_SVAL(IOV_TXGLOMSIZE):
            if (int_val > SDPCM_MAXGLOM_SIZE) {
                bcmerror = BCME_ERROR;
            } else {
                bus->txglomsize = (uint)int_val;
            }
            break;
        case IOV_SVAL(IOV_HANGREPORT):
            bus->dhd->hang_report = bool_val;
            DHD_ERROR(("%s: Set hang_report as %d\n", __FUNCTION__,
                       bus->dhd->hang_report));
            break;

        case IOV_GVAL(IOV_HANGREPORT):
            int_val = (int32)bus->dhd->hang_report;
            bcopy(&int_val, arg, val_size);
            break;

        case IOV_GVAL(IOV_TXINRX_THRES):
            int_val = bus->txinrx_thres;
            bcopy(&int_val, arg, val_size);
            break;
        case IOV_SVAL(IOV_TXINRX_THRES):
            if (int_val < 0) {
                bcmerror = BCME_BADARG;
            } else {
                bus->txinrx_thres = int_val;
            }
            break;

        case IOV_GVAL(IOV_SDIO_SUSPEND):
            int_val = (bus->dhd->busstate == DHD_BUS_SUSPEND) ? 1 : 0;
            bcopy(&int_val, arg, val_size);
            break;

        case IOV_SVAL(IOV_SDIO_SUSPEND):
            if (bool_val) { /* Suspend */
                dhdsdio_suspend(bus);
            } else { /* Resume */
                dhdsdio_resume(bus);
            }
            break;

#if defined(DEBUGGER) || defined(DHD_DSCOPE)
        case IOV_SVAL(IOV_GDB_SERVER):
            if (bool_val == TRUE) {
                debugger_init((void *)bus, &bus_ops, int_val,
                              SI_ENUM_BASE(bus->sih));
            } else {
                debugger_close();
            }
            break;
#endif /* DEBUGGER || DHD_DSCOPE */

        default:
            bcmerror = BCME_UNSUPPORTED;
            break;
    }

exit:
    if ((bus->idletime == DHD_IDLE_IMMEDIATE) && !bus->dpc_sched &&
        NO_OTHER_ACTIVE_BUS_USER(bus)) {
        bus->activity = FALSE;
        dhdsdio_bussleep(bus, TRUE);
        dhdsdio_clkctl(bus, CLK_NONE, FALSE);
    }

    dhd_os_sdunlock(bus->dhd);

    return bcmerror;
}

static int dhdsdio_write_vars(dhd_bus_t *bus)
{
    int bcmerror = 0;
    uint32 varsize, phys_size;
    uint32 varaddr;
    uint8 *vbuffer;
    uint32 varsizew;
#ifdef DHD_DEBUG
    uint8 *nvram_ularray;
#endif /* DHD_DEBUG */

    /* Even if there are no vars are to be written, we still need to set the
     * ramsize. */
    varsize = bus->varsz ? ROUNDUP(bus->varsz, 0x4) : 0;
    varaddr = (bus->ramsize - 0x4) - varsize;

    // terence 20150412: fix for nvram failed to download
    if (bus->dhd->conf->chip == BCM43340_CHIP_ID ||
        bus->dhd->conf->chip == BCM43341_CHIP_ID) {
        varsize = varsize ? ROUNDUP(varsize, 0x40) : 0;
        varaddr = (bus->ramsize - 0x40) - varsize;
    }

    varaddr += bus->dongle_ram_base;

    if (bus->vars) {
        if ((bus->sih->buscoretype == SDIOD_CORE_ID) && (bus->sdpcmrev == 0x7)) {
            if (((varaddr & 0x3C) == 0x3C) && (varsize > 0x4)) {
                DHD_ERROR(("PR85623WAR in place\n"));
                varsize += 0x4;
                varaddr -= 0x4;
            }
        }

        vbuffer = (uint8 *)MALLOC(bus->dhd->osh, varsize);
        if (!vbuffer) {
            return BCME_NOMEM;
        }

        bzero(vbuffer, varsize);
        bcopy(bus->vars, vbuffer, bus->varsz);

        /* Write the vars list */
        bcmerror = dhdsdio_membytes(bus, TRUE, varaddr, vbuffer, varsize);
        if (bcmerror) {
            DHD_ERROR(("%s: error %d on writing %d membytes at 0x%08x\n",
                       __FUNCTION__, bcmerror, varsize, varaddr));
            return bcmerror;
        }

#ifdef DHD_DEBUG
        /* Verify NVRAM bytes */
        DHD_INFO(("Compare NVRAM dl & ul; varsize=%d\n", varsize));
        nvram_ularray = (uint8 *)MALLOC(bus->dhd->osh, varsize);
        if (!nvram_ularray) {
            MFREE(bus->dhd->osh, vbuffer, varsize);
            return BCME_NOMEM;
        }

        /* Upload image to verify downloaded contents. */
        memset(nvram_ularray, 0xaa, varsize);

        /* Read the vars list to temp buffer for comparison */
        bcmerror =
            dhdsdio_membytes(bus, FALSE, varaddr, nvram_ularray, varsize);
        if (bcmerror) {
            DHD_ERROR(("%s: error %d on reading %d nvram bytes at 0x%08x\n",
                       __FUNCTION__, bcmerror, varsize, varaddr));
        }
        /* Compare the org NVRAM with the one read from RAM */
        if (memcmp(vbuffer, nvram_ularray, varsize)) {
            DHD_ERROR(
                ("%s: Downloaded NVRAM image is corrupted.\n", __FUNCTION__));
        } else {
            DHD_ERROR(("%s: Download, Upload and compare of NVRAM succeeded.\n",
                       __FUNCTION__));
        }

        MFREE(bus->dhd->osh, nvram_ularray, varsize);
#endif /* DHD_DEBUG */

        MFREE(bus->dhd->osh, vbuffer, varsize);
    }

#ifdef MINIME
    phys_size = bus->ramsize;
#else
    phys_size = REMAP_ENAB(bus) ? bus->ramsize : bus->orig_ramsize;
#endif

    phys_size += bus->dongle_ram_base;

    /* adjust to the user specified RAM */
    DHD_INFO(("Physical memory size: %d, usable memory size: %d\n", phys_size,
              bus->ramsize));
    DHD_INFO(("Vars are at %d, orig varsize is %d\n", varaddr, varsize));
    varsize = ((phys_size - 0x4) - varaddr);

    /*
     * Determine the length token:
     * Varsize, converted to words, in lower 16-bits, checksum in upper 16-bits.
     */
#ifdef DHD_DEBUG
    if (bcmerror) {
        varsizew = 0;
    } else
#endif /* DHD_DEBUG */
    {
        varsizew = varsize / 0x4;
        varsizew = (~varsizew << 0x10) | (varsizew & 0x0000FFFF);
        varsizew = htol32(varsizew);
    }

    DHD_INFO(("New varsize is %d, length token=0x%08x\n", varsize, varsizew));

    /* Write the length token to the last word */
    bcmerror =
        dhdsdio_membytes(bus, TRUE, (phys_size - 0x4), (uint8 *)&varsizew, 0x4);

    return bcmerror;
}

bool dhd_bus_is_multibp_capable(struct dhd_bus *bus)
{
    return MULTIBP_CAP(bus->sih);
}

static int dhdsdio_download_state(dhd_bus_t *bus, bool enter)
{
    uint retries;
    int bcmerror = 0;
    int foundcr4 = 0;

    if (!bus->sih) {
        return BCME_ERROR;
    }
    /* To enter download state, disable ARM and reset SOCRAM.
     * To exit download state, simply reset ARM (default is RAM boot).
     */
    if (enter) {
        bus->alp_only = TRUE;

        if (!(si_setcore(bus->sih, ARM7S_CORE_ID, 0)) &&
            !(si_setcore(bus->sih, ARMCM3_CORE_ID, 0))) {
            if (si_setcore(bus->sih, ARMCR4_CORE_ID, 0)) {
                foundcr4 = 1;
            } else {
                DHD_ERROR(("%s: Failed to find ARM core!\n", __FUNCTION__));
                bcmerror = BCME_ERROR;
                goto fail;
            }
        }

        if (!foundcr4) {
            si_core_disable(bus->sih, 0);
            if (bcmsdh_regfail(bus->sdh)) {
                bcmerror = BCME_SDIO_ERROR;
                goto fail;
            }

            if (!(si_setcore(bus->sih, SOCRAM_CORE_ID, 0))) {
                DHD_ERROR(("%s: Failed to find SOCRAM core!\n", __FUNCTION__));
                bcmerror = BCME_ERROR;
                goto fail;
            }

            si_core_reset(bus->sih, 0, 0);
            if (bcmsdh_regfail(bus->sdh)) {
                DHD_ERROR(
                    ("%s: Failure trying reset SOCRAM core?\n", __FUNCTION__));
                bcmerror = BCME_SDIO_ERROR;
                goto fail;
            }

            /* Disable remap for download */
            if (REMAP_ENAB(bus) && si_socdevram_remap_isenb(bus->sih)) {
                dhdsdio_devram_remap(bus, FALSE);
            }

            if (CHIPID(bus->sih->chip) == BCM43430_CHIP_ID ||
                CHIPID(bus->sih->chip) == BCM43018_CHIP_ID) {
                /* Disabling Remap for SRAM_3 */
                si_socram_set_bankpda(bus->sih, 0x3, 0x0);
            }

            /* Clear the top bit of memory */
            if (bus->ramsize) {
                uint32 zeros = 0;
                if (dhdsdio_membytes(bus, TRUE, bus->ramsize - 0x4,
                                     (uint8 *)&zeros, 0x4) < 0) {
                    bcmerror = BCME_SDIO_ERROR;
                    goto fail;
                }
            }
        } else {
            /* For CR4,
             * Halt ARM
             * Remove ARM reset
             * Read RAM base address [0x18_0000]
             * [next] Download firmware
             * [done at else] Populate the reset vector
             * [done at else] Remove ARM halt
             */
            /* Halt ARM & remove reset */
            si_core_reset(bus->sih, SICF_CPUHALT, SICF_CPUHALT);
        }
    } else {
        if (!si_setcore(bus->sih, ARMCR4_CORE_ID, 0)) {
            if (!(si_setcore(bus->sih, SOCRAM_CORE_ID, 0))) {
                DHD_ERROR(("%s: Failed to find SOCRAM core!\n", __FUNCTION__));
                bcmerror = BCME_ERROR;
                goto fail;
            }

            if (!si_iscoreup(bus->sih)) {
                DHD_ERROR(
                    ("%s: SOCRAM core is down after reset?\n", __FUNCTION__));
                bcmerror = BCME_ERROR;
                goto fail;
            }

            if ((bcmerror = dhdsdio_write_vars(bus))) {
                DHD_ERROR(("%s: could not write vars to RAM\n", __FUNCTION__));
                goto fail;
            }

            /* Enable remap before ARM reset but after vars.
             * No backplane access in remap mode
             */
            if (REMAP_ENAB(bus) && !si_socdevram_remap_isenb(bus->sih)) {
                dhdsdio_devram_remap(bus, TRUE);
            }
#ifdef BCMSDIOLITE
            if (!si_setcore(bus->sih, CC_CORE_ID, 0)) {
                DHD_ERROR(
                    ("%s: Can't set to Chip Common core?\n", __FUNCTION__));
                bcmerror = BCME_ERROR;
                goto fail;
            }
#else
            if (!si_setcore(bus->sih, PCMCIA_CORE_ID, 0) &&
                !si_setcore(bus->sih, SDIOD_CORE_ID, 0)) {
                DHD_ERROR(
                    ("%s: Can't change back to SDIO core?\n", __FUNCTION__));
                bcmerror = BCME_ERROR;
                goto fail;
            }
#endif // endif
            W_SDREG(0xFFFFFFFF, &bus->regs->intstatus, retries);

            if (!(si_setcore(bus->sih, ARM7S_CORE_ID, 0)) &&
                !(si_setcore(bus->sih, ARMCM3_CORE_ID, 0))) {
                DHD_ERROR(("%s: Failed to find ARM core!\n", __FUNCTION__));
                bcmerror = BCME_ERROR;
                goto fail;
            }
        } else {
            /* cr4 has no socram, but tcm's */
            /* write vars */
            if ((bcmerror = dhdsdio_write_vars(bus))) {
                DHD_ERROR(("%s: could not write vars to RAM\n", __FUNCTION__));
                goto fail;
            }
#ifdef BCMSDIOLITE
            if (!si_setcore(bus->sih, CC_CORE_ID, 0)) {
                DHD_ERROR(
                    ("%s: Can't set to Chip Common core?\n", __FUNCTION__));
                bcmerror = BCME_ERROR;
                goto fail;
            }
#else
            if (!si_setcore(bus->sih, PCMCIA_CORE_ID, 0) &&
                !si_setcore(bus->sih, SDIOD_CORE_ID, 0)) {
                DHD_ERROR(
                    ("%s: Can't change back to SDIO core?\n", __FUNCTION__));
                bcmerror = BCME_ERROR;
                goto fail;
            }
#endif // endif
            W_SDREG(0xFFFFFFFF, &bus->regs->intstatus, retries);

            /* switch back to arm core again */
            if (!(si_setcore(bus->sih, ARMCR4_CORE_ID, 0))) {
                DHD_ERROR(("%s: Failed to find ARM CR4 core!\n", __FUNCTION__));
                bcmerror = BCME_ERROR;
                goto fail;
            }
            /* write address 0 with reset instruction */
            bcmerror = dhdsdio_membytes(bus, TRUE, 0, (uint8 *)&bus->resetinstr,
                                        sizeof(bus->resetinstr));
            if (bcmerror == BCME_OK) {
                uint32 tmp;
                /* verify write */
                bcmerror =
                    dhdsdio_membytes(bus, FALSE, 0, (uint8 *)&tmp, sizeof(tmp));
                if (bcmerror == BCME_OK && tmp != bus->resetinstr) {
                    DHD_ERROR(("%s: Failed to write 0x%08x to addr 0\n",
                               __FUNCTION__, bus->resetinstr));
                    DHD_ERROR(("%s: contents of addr 0 is 0x%08x\n",
                               __FUNCTION__, tmp));
                    bcmerror = BCME_SDIO_ERROR;
                    goto fail;
                }
            }
            /* now remove reset and halt and continue to run CR4 */
        }

        si_core_reset(bus->sih, 0, 0);
        if (bcmsdh_regfail(bus->sdh)) {
            DHD_ERROR(
                ("%s: Failure trying to reset ARM core?\n", __FUNCTION__));
            bcmerror = BCME_SDIO_ERROR;
            goto fail;
        }

        /* Allow HT Clock now that the ARM is running. */
        bus->alp_only = FALSE;

        bus->dhd->busstate = DHD_BUS_LOAD;
    }

fail:
    /* Always return to SDIOD core */
    if (!si_setcore(bus->sih, PCMCIA_CORE_ID, 0)) {
        si_setcore(bus->sih, SDIOD_CORE_ID, 0);
    }

    return bcmerror;
}

int dhd_bus_iovar_op(dhd_pub_t *dhdp, const char *name, void *params, int plen,
                     void *arg, int len, bool set)
{
    dhd_bus_t *bus = dhdp->bus;
    const bcm_iovar_t *vi = NULL;
    int bcmerror = 0;
    int val_size;
    uint32 actionid;

    DHD_TRACE(("%s: Enter\n", __FUNCTION__));

    ASSERT(name);
    ASSERT(len >= 0);

    /* Get MUST have return space */
    ASSERT(set || (arg && len));

    /* Set does NOT take qualifiers */
    ASSERT(!set || (!params && !plen));

    /* Look up var locally; if not found pass to host driver */
    if ((vi = bcm_iovar_lookup(dhdsdio_iovars, name)) == NULL) {
        dhd_os_sdlock(bus->dhd);

        BUS_WAKE(bus);

        /* Turn on clock in case SD command needs backplane */
        dhdsdio_clkctl(bus, CLK_AVAIL, FALSE);

        bcmerror = bcmsdh_iovar_op(bus->sdh, name, params, plen, arg, len, set);

        /* Check for bus configuration changes of interest */

        /* If it was divisor change, read the new one */
        if (set && strcmp(name, "sd_divisor") == 0) {
            if (bcmsdh_iovar_op(bus->sdh, "sd_divisor", NULL, 0,
                                &bus->sd_divisor, sizeof(int32),
                                FALSE) != BCME_OK) {
                bus->sd_divisor = -1;
                DHD_ERROR(("%s: fail on %s get\n", __FUNCTION__, name));
            } else {
                DHD_INFO(("%s: noted %s update, value now %d\n", __FUNCTION__,
                          name, bus->sd_divisor));
            }
        }
        /* If it was a mode change, read the new one */
        if (set && strcmp(name, "sd_mode") == 0) {
            if (bcmsdh_iovar_op(bus->sdh, "sd_mode", NULL, 0, &bus->sd_mode,
                                sizeof(int32), FALSE) != BCME_OK) {
                bus->sd_mode = -1;
                DHD_ERROR(("%s: fail on %s get\n", __FUNCTION__, name));
            } else {
                DHD_INFO(("%s: noted %s update, value now %d\n", __FUNCTION__,
                          name, bus->sd_mode));
            }
        }
        /* Similar check for blocksize change */
        if (set && strcmp(name, "sd_blocksize") == 0) {
            int32 fnum = 0x2;
            if (bcmsdh_iovar_op(bus->sdh, "sd_blocksize", &fnum, sizeof(int32),
                                &bus->blocksize, sizeof(int32),
                                FALSE) != BCME_OK) {
                bus->blocksize = 0;
                DHD_ERROR(("%s: fail on fn %d %s get\n", __FUNCTION__, fnum,
                           "sd_blocksize"));
            } else {
                DHD_INFO(("%s: noted fn %d %s update, value now %d\n",
                          __FUNCTION__, fnum, "sd_blocksize", bus->blocksize));

                dhdsdio_tune_fifoparam(bus);
            }
        }
        bus->roundup = MIN(max_roundup, bus->blocksize);

        if ((bus->idletime == DHD_IDLE_IMMEDIATE) && !bus->dpc_sched &&
            NO_OTHER_ACTIVE_BUS_USER(bus)) {
            bus->activity = FALSE;
            dhdsdio_bussleep(bus, TRUE);
            dhdsdio_clkctl(bus, CLK_NONE, FALSE);
        }

        dhd_os_sdunlock(bus->dhd);
        goto exit;
    }

    DHD_CTL(("%s: %s %s, len %d plen %d\n", __FUNCTION__, name,
             (set ? "set" : "get"), len, plen));

    /* set up 'params' pointer in case this is a set command so that
     * the convenience int and bool code can be common to set and get
     */
    if (params == NULL) {
        params = arg;
        plen = len;
    }

    if (vi->type == IOVT_VOID) {
        val_size = 0;
    } else if (vi->type == IOVT_BUFFER) {
        val_size = len;
    } else {
        /* all other types are integer sized */
        val_size = sizeof(int);
    }

    actionid = set ? IOV_SVAL(vi->varid) : IOV_GVAL(vi->varid);
    bcmerror = dhdsdio_doiovar(bus, vi, actionid, name, params, plen, arg, len,
                               val_size);

exit:
    return bcmerror;
}

void dhd_bus_stop(struct dhd_bus *bus, bool enforce_mutex)
{
    osl_t *osh;
    uint32 local_hostintmask;
    uint8 saveclk;
    uint retries;
    int err;
    bool wlfc_enabled = FALSE;
    unsigned long flags;

    if (!bus->dhd) {
        return;
    }

    osh = bus->dhd->osh;
    DHD_TRACE(("%s: Enter\n", __FUNCTION__));

    bcmsdh_waitlockfree(bus->sdh);

    if (enforce_mutex) {
        dhd_os_sdlock(bus->dhd);
    }

    if ((bus->dhd->busstate == DHD_BUS_DOWN) || bus->dhd->hang_was_sent) {
        /* if Firmware already hangs disbale any interrupt */
        DHD_ERROR(("%s: making DHD_BUS_DOWN\n", __FUNCTION__));
        bus->dhd->busstate = DHD_BUS_DOWN;
        bus->hostintmask = 0;
        bcmsdh_intr_disable(bus->sdh);
    } else {
        BUS_WAKE(bus);
        if (KSO_ENAB(bus)) {
            /* Enable clock for device interrupts */
            dhdsdio_clkctl(bus, CLK_AVAIL, FALSE);

            /* Disable and clear interrupts at the chip level also */
            W_SDREG(0, &bus->regs->hostintmask, retries);
            local_hostintmask = bus->hostintmask;
            bus->hostintmask = 0;

            /* Force clocks on backplane to be sure F2 interrupt propagates */
            saveclk = bcmsdh_cfg_read(bus->sdh, SDIO_FUNC_1,
                                      SBSDIO_FUNC1_CHIPCLKCSR, &err);
            if (!err) {
                bcmsdh_cfg_write(bus->sdh, SDIO_FUNC_1, SBSDIO_FUNC1_CHIPCLKCSR,
                                 (saveclk | SBSDIO_FORCE_HT), &err);
            }
            if (err) {
                DHD_ERROR(("%s: Failed to force clock for F2: err %d\n",
                           __FUNCTION__, err));
            }

            /* Turn off the bus (F2), free any pending packets */
            DHD_INTR(("%s: disable SDIO interrupts\n", __FUNCTION__));
            bcmsdh_intr_disable(bus->sdh);
#ifndef BCMSPI
            bcmsdh_cfg_write(bus->sdh, SDIO_FUNC_0, SDIOD_CCCR_IOEN,
                             SDIO_FUNC_ENABLE_1, NULL);
#endif /* !BCMSPI */

            /* Clear any pending interrupts now that F2 is disabled */
            W_SDREG(local_hostintmask, &bus->regs->intstatus, retries);
        }

        /* Turn off the backplane clock (only) */
        dhdsdio_clkctl(bus, CLK_SDONLY, FALSE);

        /* Change our idea of bus state */
        DHD_LINUX_GENERAL_LOCK(bus->dhd, flags);
        DHD_ERROR(("%s: making DHD_BUS_DOWN\n", __FUNCTION__));
        bus->dhd->busstate = DHD_BUS_DOWN;
        DHD_LINUX_GENERAL_UNLOCK(bus->dhd, flags);
    }

#ifdef PROP_TXSTATUS
    wlfc_enabled =
        (dhd_wlfc_cleanup_txq(bus->dhd, NULL, 0) != WLFC_UNSUPPORTED);
#endif // endif
    if (!wlfc_enabled) {
#ifdef DHDTCPACK_SUPPRESS
        /* Clean tcp_ack_info_tbl in order to prevent access to flushed pkt,
         * when there is a newly coming packet from network stack.
         */
        dhd_tcpack_info_tbl_clean(bus->dhd);
#endif /* DHDTCPACK_SUPPRESS */
        dhd_os_sdlock_txq(bus->dhd);
        /* Clear the data packet queues */
        pktq_flush(osh, &bus->txq, TRUE);
        dhd_os_sdunlock_txq(bus->dhd);
    }

    /* Clear any held glomming stuff */
    if (bus->glomd) {
        PKTFREE(osh, bus->glomd, FALSE);
    }

    if (bus->glom) {
        PKTFREE(osh, bus->glom, FALSE);
    }

    bus->glom = bus->glomd = NULL;

    /* Clear rx control and wake any waiters */
    bus->rxlen = 0;
    dhd_os_ioctl_resp_wake(bus->dhd);

    /* Reset some F2 state stuff */
    bus->rxskip = FALSE;
    bus->tx_seq = bus->rx_seq = 0;

    bus->tx_max = 0x4;

    if (enforce_mutex) {
        dhd_os_sdunlock(bus->dhd);
    }
}

#if defined(BCMSDIOH_TXGLOM) && defined(BCMSDIOH_STD)
extern uint sd_txglom;
#endif // endif
void dhd_txglom_enable(dhd_pub_t *dhdp, bool enable)
{
    /* can't enable host txglom by default, some platforms have no
     * (or crappy) ADMA support and txglom will cause kernel assertions (e.g.
     * panda board)
     */
    dhd_bus_t *bus = dhdp->bus;
#ifdef BCMSDIOH_TXGLOM
    uint32 rxglom;
    int32 ret;

    DHD_TRACE(("%s: Enter\n", __FUNCTION__));

#ifdef BCMSDIOH_STD
    if (enable) {
        enable = sd_txglom;
    }
#endif /* BCMSDIOH_STD */

    if (enable) {
        rxglom = 1;
        ret = dhd_iovar(dhdp, 0, "bus:rxglom", (char *)&rxglom, sizeof(rxglom),
                        NULL, 0, TRUE);
        if (ret >= 0) {
            bus->txglom_enable = TRUE;
        } else {
#ifdef BCMSDIOH_STD
            sd_txglom = 0;
#endif /* BCMSDIOH_STD */
            bus->txglom_enable = FALSE;
        }
    } else
#endif /* BCMSDIOH_TXGLOM */
        bus->txglom_enable = FALSE;
    printf("%s: enable %d\n", __FUNCTION__, bus->txglom_enable);
    dhd_conf_set_txglom_params(bus->dhd, bus->txglom_enable);
    bcmsdh_set_mode(bus->sdh, bus->dhd->conf->txglom_mode);
}

int dhd_bus_init(dhd_pub_t *dhdp, bool enforce_mutex)
{
    dhd_bus_t *bus = dhdp->bus;
    dhd_timeout_t tmo;
    uint retries = 0;
    uint8 ready, enable;
    int err, ret = 0;
#ifdef BCMSPI
    uint32 dstatus = 0; /* gSPI device-status bits */
#else                   /* BCMSPI */
    uint8 saveclk;
#endif                  /* BCMSPI */
#if defined(SDIO_ISR_THREAD)
    int intr_extn;
#endif

    DHD_TRACE(("%s: Enter\n", __FUNCTION__));

    ASSERT(bus->dhd);
    if (!bus->dhd) {
        return 0;
    }

    if (enforce_mutex) {
        dhd_os_sdlock(bus->dhd);
    }

    if (bus->sih->chip == BCM43362_CHIP_ID) {
        printf("%s: delay 100ms for BCM43362\n", __FUNCTION__);
        OSL_DELAY(0x186A0); // terence 20131209: delay for 43362
    }

    /* Make sure backplane clock is on, needed to generate F2 interrupt */
    dhdsdio_clkctl(bus, CLK_AVAIL, FALSE);
    if (bus->clkstate != CLK_AVAIL) {
        DHD_ERROR(("%s: clock state is wrong. state = %d\n", __FUNCTION__,
                   bus->clkstate));
        ret = -1;
        goto exit;
    }

#ifdef BCMSPI
    /* fake "ready" for spi, wake-wlan would have already enabled F1 and F2 */
    ready = (SDIO_FUNC_ENABLE_1 | SDIO_FUNC_ENABLE_2);
    enable = 0;

    /* Give the dongle some time to do its thing and set IOR2 */
    dhd_timeout_start(&tmo, WAIT_F2RXFIFORDY * WAIT_F2RXFIFORDY_DELAY * 0x3E8);
    while (!enable && !dhd_timeout_expired(&tmo)) {
        dstatus =
            bcmsdh_cfg_read_word(bus->sdh, SDIO_FUNC_0, SPID_STATUS_REG, NULL);
        if (dstatus & STATUS_F2_RX_READY) {
            enable = TRUE;
        }
    }

    if (enable) {
        DHD_ERROR(("Took %u usec before dongle is ready\n", tmo.elapsed));
        enable = ready;
    } else {
        DHD_ERROR(
            ("dstatus when timed out on f2-fifo not ready = 0x%x\n", dstatus));
        DHD_ERROR(("Waited %u usec, dongle is not ready\n", tmo.elapsed));
        ret = -1;
        goto exit;
    }

#else /* !BCMSPI */
    /* Force clocks on backplane to be sure F2 interrupt propagates */
    saveclk =
        bcmsdh_cfg_read(bus->sdh, SDIO_FUNC_1, SBSDIO_FUNC1_CHIPCLKCSR, &err);

    if (!err) {
        if (bus->sih->chip == BCM43012_CHIP_ID) {
            bcmsdh_cfg_write(bus->sdh, SDIO_FUNC_1, SBSDIO_FUNC1_CHIPCLKCSR,
                             (saveclk | SBSDIO_HT_AVAIL_REQ), &err);
        } else {
            bcmsdh_cfg_write(bus->sdh, SDIO_FUNC_1, SBSDIO_FUNC1_CHIPCLKCSR,
                             (saveclk | SBSDIO_FORCE_HT), &err);
        }
    }

    if (err) {
        DHD_ERROR(
            ("%s: Failed to force clock for F2: err %d\n", __FUNCTION__, err));
        ret = -1;
        goto exit;
    }

    /* Enable function 0x2 (frame transfers) */
    W_SDREG((SDPCM_PROT_VERSION << SMB_DATA_VERSION_SHIFT),
            &bus->regs->tosbmailboxdata, retries);
    enable = (SDIO_FUNC_ENABLE_1 | SDIO_FUNC_ENABLE_2);

    bcmsdh_cfg_write(bus->sdh, SDIO_FUNC_0, SDIOD_CCCR_IOEN, enable, NULL);

    /* Give the dongle some time to do its thing and set IOR2 */
    dhd_timeout_start(&tmo, DHD_WAIT_F2RDY * 0x3E8);

    ready = 0;
    while (ready != enable && !dhd_timeout_expired(&tmo)) {
        ready = bcmsdh_cfg_read(bus->sdh, SDIO_FUNC_0, SDIOD_CCCR_IORDY, NULL);
    }

#endif /* !BCMSPI */

    DHD_ERROR(("%s: enable 0x%02x, ready 0x%02x (waited %uus)\n", __FUNCTION__,
               enable, ready, tmo.elapsed));

#if defined(SDIO_ISR_THREAD)
    if (dhdp->conf->intr_extn) {
        intr_extn =
            bcmsdh_cfg_read(bus->sdh, SDIO_FUNC_0, SDIOD_CCCR_INTR_EXTN, NULL);
        if (intr_extn & 0x1) {
            intr_extn |= 0x2;
            bcmsdh_cfg_write(bus->sdh, SDIO_FUNC_0, SDIOD_CCCR_INTR_EXTN,
                             intr_extn, NULL);
        }
    }
#endif

    /* If F2 successfully enabled, set core and enable interrupts */
    if (ready == enable) {
        /* Make sure we're talking to the core. */
#ifdef BCMSDIOLITE
        bus->regs = si_setcore(bus->sih, CC_CORE_ID, 0);
        ASSERT(bus->regs != NULL);
#else
        if (!(bus->regs = si_setcore(bus->sih, PCMCIA_CORE_ID, 0))) {
            bus->regs = si_setcore(bus->sih, SDIOD_CORE_ID, 0);
        }
        ASSERT(bus->regs != NULL);
#endif // endif
        /* Set up the interrupt mask and enable interrupts */
        bus->hostintmask = HOSTINTMASK;
        /* corerev 4 could use the newer interrupt logic to detect the frames */
#ifndef BCMSPI
        if ((bus->sih->buscoretype == SDIOD_CORE_ID) && (bus->sdpcmrev == 0x4) &&
            (bus->rxint_mode != SDIO_DEVICE_HMB_RXINT)) {
            bus->hostintmask &= ~I_HMB_FRAME_IND;
            bus->hostintmask |= I_XMTDATA_AVAIL;
        }
#endif /* BCMSPI */
        W_SDREG(bus->hostintmask, &bus->regs->hostintmask, retries);

        if (bus->sih->buscorerev < 0xF) {
            bcmsdh_cfg_write(bus->sdh, SDIO_FUNC_1, SBSDIO_WATERMARK,
                             (uint8)watermark, &err);
        }

        /* Set bus state according to enable result */
        dhdp->busstate = DHD_BUS_DATA;

        bus->intdis = FALSE;
        if (bus->intr) {
            DHD_INTR(("%s: enable SDIO device interrupts\n", __FUNCTION__));
            bcmsdh_intr_enable(bus->sdh);
        } else {
            DHD_INTR(("%s: disable SDIO interrupts\n", __FUNCTION__));
            bcmsdh_intr_disable(bus->sdh);
        }
    }

#ifndef BCMSPI

    else {
        /* Disable F2 again */
        enable = SDIO_FUNC_ENABLE_1;
        bcmsdh_cfg_write(bus->sdh, SDIO_FUNC_0, SDIOD_CCCR_IOEN, enable, NULL);
    }

    if (dhdsdio_sr_cap(bus)) {
        dhdsdio_sr_init(bus);
        /* Masking the chip active interrupt  permanantly */
        bus->hostintmask &= ~I_CHIPACTIVE;
        W_SDREG(bus->hostintmask, &bus->regs->hostintmask, retries);
        DHD_INFO(("%s: disable I_CHIPACTIVE in hostintmask[0x%08x]\n",
                  __FUNCTION__, bus->hostintmask));
    } else {
        bcmsdh_cfg_write(bus->sdh, SDIO_FUNC_1, SBSDIO_FUNC1_CHIPCLKCSR,
                         saveclk, &err);
    }
#endif /* !BCMSPI */

    /* If we didn't come up, turn off backplane clock */
    if (dhdp->busstate != DHD_BUS_DATA) {
        dhdsdio_clkctl(bus, CLK_NONE, FALSE);
    }

exit:
    if (enforce_mutex) {
        dhd_os_sdunlock(bus->dhd);
    }

    return ret;
}

static void dhdsdio_rxfail(dhd_bus_t *bus, bool abort, bool rtx)
{
    bcmsdh_info_t *sdh = bus->sdh;
    sdpcmd_regs_t *regs = bus->regs;
    uint retries = 0;
    uint16 lastrbc;
    uint8 hi, lo;
    int err;

    DHD_ERROR(("%s: %sterminate frame%s\n", __FUNCTION__,
               (abort ? "abort command, " : ""), (rtx ? ", send NAK" : "")));

    if (!KSO_ENAB(bus)) {
        DHD_ERROR(("%s: Device asleep\n", __FUNCTION__));
        return;
    }

    if (abort) {
        bcmsdh_abort(sdh, SDIO_FUNC_2);
    }

    bcmsdh_cfg_write(sdh, SDIO_FUNC_1, SBSDIO_FUNC1_FRAMECTRL, SFC_RF_TERM,
                     &err);
    if (err) {
        DHD_ERROR(("%s: SBSDIO_FUNC1_FRAMECTRL cmd err\n", __FUNCTION__));
        goto fail;
    }
    bus->f1regdata++;

    /* Wait until the packet has been flushed (device/FIFO stable) */
    for (lastrbc = retries = 0xffff; retries > 0; retries--) {
        hi = bcmsdh_cfg_read(sdh, SDIO_FUNC_1, SBSDIO_FUNC1_RFRAMEBCHI, NULL);
        lo = bcmsdh_cfg_read(sdh, SDIO_FUNC_1, SBSDIO_FUNC1_RFRAMEBCLO, &err);
        if (err) {
            DHD_ERROR(("%s: SBSDIO_FUNC1_RFAMEBCLO cmd err\n", __FUNCTION__));
            goto fail;
        }

        bus->f1regdata += 0x2;

        if ((hi == 0) && (lo == 0)) {
            break;
        }

        if ((hi > (lastrbc >> 0x8)) && (lo > (lastrbc & 0x00ff))) {
            DHD_ERROR(("%s: count growing: last 0x%04x now 0x%04x\n",
                       __FUNCTION__, lastrbc, ((hi << 0x8) + lo)));
        }
        lastrbc = (hi << 0x8) + lo;
    }

    if (!retries) {
        DHD_ERROR(
            ("%s: count never zeroed: last 0x%04x\n", __FUNCTION__, lastrbc));
    } else {
        DHD_INFO(("%s: flush took %d iterations\n", __FUNCTION__,
                  (0xffff - retries)));
    }

    if (rtx) {
        bus->rxrtx++;
        W_SDREG(SMB_NAK, &regs->tosbmailbox, retries);
        bus->f1regdata++;
        if (retries <= retry_limit) {
            bus->rxskip = TRUE;
        }
    }

    /* Clear partial in any case */
    bus->nextlen = 0;

fail:
    /* If we can't reach the device, signal failure */
    if (err || bcmsdh_regfail(sdh)) {
        DHD_ERROR(("%s: making DHD_BUS_DOWN\n", __FUNCTION__));
        bus->dhd->busstate = DHD_BUS_DOWN;
    }
}

static void dhdsdio_read_control(dhd_bus_t *bus, uint8 *hdr, uint len,
                                 uint doff)
{
    bcmsdh_info_t *sdh = bus->sdh;
    uint rdlen, pad;

    int sdret;

    DHD_TRACE(("%s: Enter\n", __FUNCTION__));

    /* Control data already received in aligned rxctl */
    if ((bus->bus == SPI_BUS) && (!bus->usebufpool)) {
        goto gotpkt;
    }

    ASSERT(bus->rxbuf);
    /* Set rxctl for frame (w/optional alignment) */
    bus->rxctl = bus->rxbuf;
    if (dhd_alignctl) {
        bus->rxctl += firstread;
        if ((pad = ((uintptr)bus->rxctl % DHD_SDALIGN))) {
            bus->rxctl += (DHD_SDALIGN - pad);
        }
        bus->rxctl -= firstread;
    }
    ASSERT(bus->rxctl >= bus->rxbuf);

    /* Copy the already-read portion over */
    bcopy(hdr, bus->rxctl, firstread);
    if (len <= firstread) {
        goto gotpkt;
    }

    /* Copy the full data pkt in gSPI case and process ioctl. */
    if (bus->bus == SPI_BUS) {
        bcopy(hdr, bus->rxctl, len);
        goto gotpkt;
    }

    /* Raise rdlen to next SDIO block to avoid tail command */
    rdlen = len - firstread;
    if (bus->roundup && bus->blocksize && (rdlen > bus->blocksize)) {
        pad = bus->blocksize - (rdlen % bus->blocksize);
        if ((pad <= bus->roundup) && (pad < bus->blocksize) &&
            ((len + pad) < bus->dhd->maxctl)) {
            rdlen += pad;
        }
    } else if (rdlen % DHD_SDALIGN) {
        rdlen += DHD_SDALIGN - (rdlen % DHD_SDALIGN);
    }

    /* Satisfy length-alignment requirements */
    if (forcealign && (rdlen & (ALIGNMENT - 1))) {
        rdlen = ROUNDUP(rdlen, ALIGNMENT);
    }

    /* Drop if the read is too big or it exceeds our maximum */
    if ((rdlen + firstread) > bus->dhd->maxctl) {
        DHD_ERROR(("%s: %d-byte control read exceeds %d-byte buffer\n",
                   __FUNCTION__, rdlen, bus->dhd->maxctl));
        bus->dhd->rx_errors++;
        dhdsdio_rxfail(bus, FALSE, FALSE);
        goto done;
    }

    if ((len - doff) > bus->dhd->maxctl) {
        DHD_ERROR(
            ("%s: %d-byte ctl frame (%d-byte ctl data) exceeds %d-byte limit\n",
             __FUNCTION__, len, (len - doff), bus->dhd->maxctl));
        bus->dhd->rx_errors++;
        bus->rx_toolong++;
        dhdsdio_rxfail(bus, FALSE, FALSE);
        goto done;
    }

    /* Read remainder of frame body into the rxctl buffer */
    sdret =
        dhd_bcmsdh_recv_buf(bus, bcmsdh_cur_sbwad(sdh), SDIO_FUNC_2, F2SYNC,
                            (bus->rxctl + firstread), rdlen, NULL, NULL, NULL);
    bus->f2rxdata++;
    ASSERT(sdret != BCME_PENDING);

    /* Control frame failures need retransmission */
    if (sdret < 0) {
        DHD_ERROR(("%s: read %d control bytes failed: %d\n", __FUNCTION__,
                   rdlen, sdret));
        bus->rxc_errors++; /* dhd.rx_ctlerrs is higher level */
        dhdsdio_rxfail(bus, TRUE, TRUE);
        goto done;
    }

gotpkt:

#ifdef DHD_DEBUG
    if (DHD_BYTES_ON() && DHD_CTL_ON()) {
        prhex("RxCtrl", bus->rxctl, len);
    }
#endif // endif

    /* Point to valid data and indicate its length */
    bus->rxctl += doff;
    bus->rxlen = len - doff;

done:
    /* Awake any waiters */
    dhd_os_ioctl_resp_wake(bus->dhd);
}
int dhd_process_pkt_reorder_info(dhd_pub_t *dhd, uchar *reorder_info_buf,
                                 uint reorder_info_len, void **pkt,
                                 uint32 *pkt_count);

static uint8 dhdsdio_rxglom(dhd_bus_t *bus, uint8 rxseq)
{
    uint16 dlen, totlen;
    uint8 *dptr, num = 0;

    uint16 sublen, check;
    void *pfirst, *plast, *pnext;
    void *list_tail[DHD_MAX_IFS] = {NULL};
    void *list_head[DHD_MAX_IFS] = {NULL};
    uint8 idx;
    osl_t *osh = bus->dhd->osh;

    int errcode;
    uint8 chan, seq, doff, sfdoff;
    uint8 txmax;
    uchar reorder_info_buf[WLHOST_REORDERDATA_TOTLEN];
    uint reorder_info_len;

    int ifidx = 0;
    bool usechain = bus->use_rxchain;

    /* If packets, issue read(s) and send up packet chain */
    /* Return sequence numbers consumed? */

    DHD_TRACE(
        ("dhdsdio_rxglom: start: glomd %p glom %p\n", bus->glomd, bus->glom));

    /* If there's a descriptor, generate the packet chain */
    if (bus->glomd) {
        dhd_os_sdlock_rxq(bus->dhd);

        pfirst = plast = pnext = NULL;
        dlen = (uint16)PKTLEN(osh, bus->glomd);
        dptr = PKTDATA(osh, bus->glomd);
        if (!dlen || (dlen & 1)) {
            DHD_ERROR(("%s: bad glomd len (%d), ignore descriptor\n",
                       __FUNCTION__, dlen));
            dlen = 0;
        }

        for (totlen = num = 0; dlen; num++) {
            /* Get (and move past) next length */
            sublen = ltoh16_ua(dptr);
            dlen -= sizeof(uint16);
            dptr += sizeof(uint16);
            if ((sublen < SDPCM_HDRLEN) ||
                ((num == 0) && (sublen < (0x2 * SDPCM_HDRLEN)))) {
                DHD_ERROR(("%s: descriptor len %d bad: %d\n", __FUNCTION__, num,
                           sublen));
                pnext = NULL;
                break;
            }
            if (sublen % DHD_SDALIGN) {
                DHD_ERROR(("%s: sublen %d not a multiple of %d\n", __FUNCTION__,
                           sublen, DHD_SDALIGN));
                usechain = FALSE;
            }
            totlen += sublen;

            /* For last frame, adjust read len so total is a block multiple */
            if (!dlen) {
                sublen += (ROUNDUP(totlen, bus->blocksize) - totlen);
                totlen = ROUNDUP(totlen, bus->blocksize);
            }

            /* Allocate/chain packet for next subframe */
            if ((pnext = PKTGET(osh, sublen + DHD_SDALIGN, FALSE)) == NULL) {
                DHD_ERROR(("%s: PKTGET failed, num %d len %d\n", __FUNCTION__,
                           num, sublen));
                break;
            }
            ASSERT(!PKTLINK(pnext));
            if (!pfirst) {
                ASSERT(!plast);
                pfirst = plast = pnext;
            } else {
                ASSERT(plast);
                PKTSETNEXT(osh, plast, pnext);
                plast = pnext;
            }

            /* Adhere to start alignment requirements */
            PKTALIGN(osh, pnext, sublen, DHD_SDALIGN);
        }

        /* If all allocations succeeded, save packet chain in bus structure */
        if (pnext) {
            DHD_GLOM(("%s: allocated %d-byte packet chain for %d subframes\n",
                      __FUNCTION__, totlen, num));
            if (DHD_GLOM_ON() && bus->nextlen) {
                if (totlen != bus->nextlen) {
                    DHD_GLOM(("%s: glomdesc mismatch: nextlen %d glomdesc %d "
                              "rxseq %d\n",
                              __FUNCTION__, bus->nextlen, totlen, rxseq));
                }
            }
            bus->glom = pfirst;
            pfirst = pnext = NULL;
        } else {
            if (pfirst) {
                PKTFREE(osh, pfirst, FALSE);
            }
            bus->glom = NULL;
            num = 0;
        }

        /* Done with descriptor packet */
        PKTFREE(osh, bus->glomd, FALSE);
        bus->glomd = NULL;
        bus->nextlen = 0;

        dhd_os_sdunlock_rxq(bus->dhd);
    }

    /* Ok -- either we just generated a packet chain, or had one from before */
    if (bus->glom) {
        if (DHD_GLOM_ON()) {
            DHD_GLOM(
                ("%s: attempt superframe read, packet chain:\n", __FUNCTION__));
            for (pnext = bus->glom; pnext; pnext = PKTNEXT(osh, pnext)) {
                DHD_GLOM(("    %p: %p len 0x%04x (%d)\n", pnext,
                          (uint8 *)PKTDATA(osh, pnext), PKTLEN(osh, pnext),
                          PKTLEN(osh, pnext)));
            }
        }

        pfirst = bus->glom;
        dlen = (uint16)pkttotlen(osh, pfirst);

        /* Do an SDIO read for the superframe.  Configurable iovar to
         * read directly into the chained packet, or allocate a large
         * packet and and copy into the chain.
         */
        if (usechain) {
            errcode = dhd_bcmsdh_recv_buf(
                bus, bcmsdh_cur_sbwad(bus->sdh), SDIO_FUNC_2, F2SYNC,
                (uint8 *)PKTDATA(osh, pfirst), dlen, pfirst, NULL, NULL);
        } else if (bus->dataptr) {
            errcode = dhd_bcmsdh_recv_buf(bus, bcmsdh_cur_sbwad(bus->sdh),
                                          SDIO_FUNC_2, F2SYNC, bus->dataptr,
                                          dlen, NULL, NULL, NULL);
            sublen = (uint16)pktfrombuf(osh, pfirst, 0, dlen, bus->dataptr);
            if (sublen != dlen) {
                DHD_ERROR(("%s: FAILED TO COPY, dlen %d sublen %d\n",
                           __FUNCTION__, dlen, sublen));
                errcode = -1;
            }
            pnext = NULL;
            BCM_REFERENCE(pnext);
        } else {
            DHD_ERROR(("COULDN'T ALLOC %d-BYTE GLOM, FORCE FAILURE\n", dlen));
            errcode = -1;
        }
        bus->f2rxdata++;
        ASSERT(errcode != BCME_PENDING);

        /* On failure, kill the superframe, allow a couple retries */
        if (errcode < 0) {
            DHD_ERROR(("%s: glom read of %d bytes failed: %d\n", __FUNCTION__,
                       dlen, errcode));
            bus->dhd->rx_errors++;

            if (bus->glomerr++ < 0x3) {
                dhdsdio_rxfail(bus, TRUE, TRUE);
            } else {
                bus->glomerr = 0;
                dhdsdio_rxfail(bus, TRUE, FALSE);
                dhd_os_sdlock_rxq(bus->dhd);
                PKTFREE(osh, bus->glom, FALSE);
                dhd_os_sdunlock_rxq(bus->dhd);
                bus->rxglomfail++;
                bus->glom = NULL;
            }
            return 0;
        }

#ifdef DHD_DEBUG
        if (DHD_GLOM_ON()) {
            prhex("SUPERFRAME", PKTDATA(osh, pfirst),
                  MIN(PKTLEN(osh, pfirst), 0x30));
        }
#endif // endif

        /* Validate the superframe header */
        dptr = (uint8 *)PKTDATA(osh, pfirst);
        sublen = ltoh16_ua(dptr);
        check = ltoh16_ua(dptr + sizeof(uint16));

        chan = SDPCM_PACKET_CHANNEL(&dptr[SDPCM_FRAMETAG_LEN]);
        seq = SDPCM_PACKET_SEQUENCE(&dptr[SDPCM_FRAMETAG_LEN]);
        bus->nextlen = dptr[SDPCM_FRAMETAG_LEN + SDPCM_NEXTLEN_OFFSET];
        if ((bus->nextlen << 0x4) > MAX_RX_DATASZ) {
            DHD_INFO(("%s: got frame w/nextlen too large (%d) seq %d\n",
                      __FUNCTION__, bus->nextlen, seq));
            bus->nextlen = 0;
        }
        doff = SDPCM_DOFFSET_VALUE(&dptr[SDPCM_FRAMETAG_LEN]);
        txmax = SDPCM_WINDOW_VALUE(&dptr[SDPCM_FRAMETAG_LEN]);

        errcode = 0;
        if ((uint16) ~(sublen ^ check)) {
            DHD_ERROR(
                ("%s (superframe): HW hdr error: len/check 0x%04x/0x%04x\n",
                 __FUNCTION__, sublen, check));
            errcode = -1;
        } else if (ROUNDUP(sublen, bus->blocksize) != dlen) {
            DHD_ERROR(
                ("%s (superframe): len 0x%04x, rounded 0x%04x, expect 0x%04x\n",
                 __FUNCTION__, sublen, ROUNDUP(sublen, bus->blocksize), dlen));
            errcode = -1;
        } else if (SDPCM_PACKET_CHANNEL(&dptr[SDPCM_FRAMETAG_LEN]) !=
                   SDPCM_GLOM_CHANNEL) {
            DHD_ERROR(("%s (superframe): bad channel %d\n", __FUNCTION__,
                       SDPCM_PACKET_CHANNEL(&dptr[SDPCM_FRAMETAG_LEN])));
            errcode = -1;
        } else if (SDPCM_GLOMDESC(&dptr[SDPCM_FRAMETAG_LEN])) {
            DHD_ERROR(
                ("%s (superframe): got second descriptor?\n", __FUNCTION__));
            errcode = -1;
        } else if ((doff < SDPCM_HDRLEN) ||
                   (doff > (PKTLEN(osh, pfirst) - SDPCM_HDRLEN))) {
            DHD_ERROR((
                "%s (superframe): Bad data offset %d: HW %d pkt %d min %d\n",
                __FUNCTION__, doff, sublen, PKTLEN(osh, pfirst), SDPCM_HDRLEN));
            errcode = -1;
        }

        /* Check sequence number of superframe SW header */
        if (rxseq != seq) {
            DHD_INFO(("%s: (superframe) rx_seq %d, expected %d\n", __FUNCTION__,
                      seq, rxseq));
            bus->rx_badseq++;
            rxseq = seq;
        }

        /* Check window for sanity */
        if ((uint8)(txmax - bus->tx_seq) > 0x70) {
            DHD_INFO(("%s: got unlikely tx max %d with tx_seq %d\n",
                      __FUNCTION__, txmax, bus->tx_seq));
            txmax = bus->tx_max;
        }
        bus->tx_max = txmax;

        /* Remove superframe header, remember offset */
        PKTPULL(osh, pfirst, doff);
        sfdoff = doff;

        /* Validate all the subframe headers */
        for (num = 0, pnext = pfirst; pnext && !errcode;
             num++, pnext = PKTNEXT(osh, pnext)) {
            dptr = (uint8 *)PKTDATA(osh, pnext);
            dlen = (uint16)PKTLEN(osh, pnext);
            sublen = ltoh16_ua(dptr);
            check = ltoh16_ua(dptr + sizeof(uint16));
            chan = SDPCM_PACKET_CHANNEL(&dptr[SDPCM_FRAMETAG_LEN]);
            doff = SDPCM_DOFFSET_VALUE(&dptr[SDPCM_FRAMETAG_LEN]);
#ifdef DHD_DEBUG
            if (DHD_GLOM_ON()) {
                prhex("subframe", dptr, 0x20);
            }
#endif // endif

            if ((uint16) ~(sublen ^ check)) {
                DHD_ERROR(("%s (subframe %d): HW hdr error: "
                           "len/check 0x%04x/0x%04x\n",
                           __FUNCTION__, num, sublen, check));
                errcode = -1;
            } else if ((sublen > dlen) || (sublen < SDPCM_HDRLEN)) {
                DHD_ERROR(("%s (subframe %d): length mismatch: "
                           "len 0x%04x, expect 0x%04x\n",
                           __FUNCTION__, num, sublen, dlen));
                errcode = -1;
            } else if ((chan != SDPCM_DATA_CHANNEL) &&
                       (chan != SDPCM_EVENT_CHANNEL)) {
                DHD_ERROR(("%s (subframe %d): bad channel %d\n", __FUNCTION__,
                           num, chan));
                errcode = -1;
            } else if ((doff < SDPCM_HDRLEN) || (doff > sublen)) {
                DHD_ERROR(
                    ("%s (subframe %d): Bad data offset %d: HW %d min %d\n",
                     __FUNCTION__, num, doff, sublen, SDPCM_HDRLEN));
                errcode = -1;
            }
        }

        if (errcode) {
            /* Terminate frame on error, request a couple retries */
            if (bus->glomerr++ < 0x3) {
                /* Restore superframe header space */
                PKTPUSH(osh, pfirst, sfdoff);
                dhdsdio_rxfail(bus, TRUE, TRUE);
            } else {
                bus->glomerr = 0;
                dhdsdio_rxfail(bus, TRUE, FALSE);
                dhd_os_sdlock_rxq(bus->dhd);
                PKTFREE(osh, bus->glom, FALSE);
                dhd_os_sdunlock_rxq(bus->dhd);
                bus->rxglomfail++;
                bus->glom = NULL;
            }
            bus->nextlen = 0;
            return 0;
        }

        /* Basic SD framing looks ok - process each packet (header) */
        bus->glom = NULL;
        plast = NULL;

        dhd_os_sdlock_rxq(bus->dhd);
        for (num = 0; pfirst; rxseq++, pfirst = pnext) {
            pnext = PKTNEXT(osh, pfirst);
            PKTSETNEXT(osh, pfirst, NULL);

            dptr = (uint8 *)PKTDATA(osh, pfirst);
            sublen = ltoh16_ua(dptr);
            chan = SDPCM_PACKET_CHANNEL(&dptr[SDPCM_FRAMETAG_LEN]);
            seq = SDPCM_PACKET_SEQUENCE(&dptr[SDPCM_FRAMETAG_LEN]);
            doff = SDPCM_DOFFSET_VALUE(&dptr[SDPCM_FRAMETAG_LEN]);

            DHD_GLOM(
                ("%s: Get subframe %d, %p(%p/%d), sublen %d chan %d seq %d\n",
                 __FUNCTION__, num, pfirst, PKTDATA(osh, pfirst),
                 PKTLEN(osh, pfirst), sublen, chan, seq));

            ASSERT((chan == SDPCM_DATA_CHANNEL) ||
                   (chan == SDPCM_EVENT_CHANNEL));

            if (rxseq != seq) {
                DHD_GLOM(
                    ("%s: rx_seq %d, expected %d\n", __FUNCTION__, seq, rxseq));
                bus->rx_badseq++;
                rxseq = seq;
            }

#ifdef DHD_DEBUG
            if (DHD_BYTES_ON() && DHD_DATA_ON()) {
                prhex("Rx Subframe Data", dptr, dlen);
            }
#endif // endif

            PKTSETLEN(osh, pfirst, sublen);
            PKTPULL(osh, pfirst, doff);

            reorder_info_len = sizeof(reorder_info_buf);

            if (PKTLEN(osh, pfirst) == 0) {
                PKTFREE(bus->dhd->osh, pfirst, FALSE);
                continue;
            } else if (dhd_prot_hdrpull(bus->dhd, &ifidx, pfirst,
                                        reorder_info_buf,
                                        &reorder_info_len) != 0) {
                DHD_ERROR(("%s: rx protocol error\n", __FUNCTION__));
                bus->dhd->rx_errors++;
                PKTFREE(osh, pfirst, FALSE);
                continue;
            }
            if (reorder_info_len) {
                uint32 free_buf_count;
                void *ppfirst;

                ppfirst = pfirst;
                /* Reordering info from the firmware */
                dhd_process_pkt_reorder_info(bus->dhd, reorder_info_buf,
                                             reorder_info_len, &ppfirst,
                                             &free_buf_count);

                if (free_buf_count == 0) {
                    continue;
                } else {
                    void *temp;

                    /*  go to the end of the chain and attach the pnext there */
                    temp = ppfirst;
                    while (PKTNEXT(osh, temp) != NULL) {
                        temp = PKTNEXT(osh, temp);
                    }
                    pfirst = temp;
                    if (list_tail[ifidx] == NULL) {
                        list_head[ifidx] = ppfirst;
                    } else {
                        PKTSETNEXT(osh, list_tail[ifidx], ppfirst);
                    }
                    list_tail[ifidx] = pfirst;
                }

                num += (uint8)free_buf_count;
            } else {
                /* this packet will go up, link back into chain and count it */

                if (list_tail[ifidx] == NULL) {
                    list_head[ifidx] = list_tail[ifidx] = pfirst;
                } else {
                    PKTSETNEXT(osh, list_tail[ifidx], pfirst);
                    list_tail[ifidx] = pfirst;
                }
                num++;
            }
#ifdef DHD_DEBUG
            if (DHD_GLOM_ON()) {
                DHD_GLOM(("%s subframe %d to stack, %p(%p/%d) nxt/lnk %p/%p\n",
                          __FUNCTION__, num, pfirst, PKTDATA(osh, pfirst),
                          PKTLEN(osh, pfirst), PKTNEXT(osh, pfirst),
                          PKTLINK(pfirst)));
                prhex("", (uint8 *)PKTDATA(osh, pfirst),
                      MIN(PKTLEN(osh, pfirst), 0x20));
            }
#endif /* DHD_DEBUG */
        }
        dhd_os_sdunlock_rxq(bus->dhd);

        for (idx = 0; idx < DHD_MAX_IFS; idx++) {
            if (list_head[idx]) {
                void *temp;
                uint8 cnt = 0;
                temp = list_head[idx];
                do {
                    temp = PKTNEXT(osh, temp);
                    cnt++;
                } while (temp);
                if (cnt) {
                    dhd_os_sdunlock(bus->dhd);
                    dhd_rx_frame(bus->dhd, idx, list_head[idx], cnt, 0);
                    dhd_os_sdlock(bus->dhd);
#if defined(SDIO_ISR_THREAD)
                    /* terence 20150615: fix for below error due to bussleep in
                     * watchdog after dhd_os_sdunlock here, so call BUS_WAKE to
                     * wake up bus again dhd_bcmsdh_recv_buf: Device asleep
                     * dhdsdio_readframes: RXHEADER FAILED: -40
                     * dhdsdio_rxfail: abort command, terminate frame, send NAK
                     */
                    BUS_WAKE(bus);
#endif
                }
            }
        }
        bus->rxglomframes++;
        bus->rxglompkts += num;
    }
    return num;
}

/* Return TRUE if there may be more frames to read */
static uint dhdsdio_readframes(dhd_bus_t *bus, uint maxframes, bool *finished)
{
    osl_t *osh = bus->dhd->osh;
    bcmsdh_info_t *sdh = bus->sdh;

    uint16 len, check;     /* Extracted hardware header fields */
    uint8 chan, seq, doff; /* Extracted software header fields */
    uint8 fcbits;          /* Extracted fcbits from software header */
    uint8 delta;

    void *pkt;       /* Packet for event or data frames */
    uint16 pad;      /* Number of pad bytes to read */
    uint16 rdlen;    /* Total number of bytes to read */
    uint8 rxseq;     /* Next sequence number to expect */
    uint rxleft = 0; /* Remaining number of frames allowed */
    int sdret;       /* Return code from bcmsdh calls */
    uint8 txmax;     /* Maximum tx sequence offered */
#ifdef BCMSPI
    uint32 dstatus = 0;  /* gSPI device status bits of */
#endif                   /* BCMSPI */
    bool len_consistent; /* Result of comparing readahead len and len from
                            hw-hdr */
    uint8 *rxbuf;
    int ifidx = 0;
    uint rxcount = 0; /* Total frames read */
    uchar reorder_info_buf[WLHOST_REORDERDATA_TOTLEN];
    uint reorder_info_len;
    uint pkt_count;

#if defined(DHD_DEBUG) || defined(SDTEST)
    bool sdtest = FALSE; /* To limit message spew from test mode */
#endif                   // endif

    DHD_TRACE(("%s: Enter\n", __FUNCTION__));
    bus->readframes = TRUE;

    if (!KSO_ENAB(bus)) {
        DHD_ERROR(("%s: KSO off\n", __FUNCTION__));
        bus->readframes = FALSE;
        return 0;
    }

    ASSERT(maxframes);

#ifdef SDTEST
    /* Allow pktgen to override maxframes */
    if (bus->pktgen_count && (bus->pktgen_mode == DHD_PKTGEN_RECV)) {
        maxframes = bus->pktgen_count;
        sdtest = TRUE;
    }
#endif // endif

    /* Not finished unless we encounter no more frames indication */
    *finished = FALSE;

#ifdef BCMSPI
    /* Get pktlen from gSPI device F0 reg. */
    if (bus->bus == SPI_BUS) {
        /* Peek in dstatus bits and find out size to do rx-read. */
        dstatus = bcmsdh_get_dstatus(bus->sdh);
        if (dstatus == 0) {
            DHD_ERROR(
                ("%s:ZERO spi dstatus, a case observed in PR61352 hit !!!\n",
                 __FUNCTION__));
        }

        DHD_TRACE(("Device status from regread = 0x%x\n", dstatus));
        DHD_TRACE(("Device status from bit-reconstruction = 0x%x\n",
                   bcmsdh_get_dstatus((void *)bus->sdh)));

        if ((dstatus & STATUS_F2_PKT_AVAILABLE) &&
            (((dstatus & STATUS_UNDERFLOW)) == 0)) {
            bus->nextlen =
                ((dstatus & STATUS_F2_PKT_LEN_MASK) >> STATUS_F2_PKT_LEN_SHIFT);
            /* '0' size with pkt-available interrupt is eqvt to 2048 bytes */
            bus->nextlen = (bus->nextlen == 0) ? SPI_MAX_PKT_LEN : bus->nextlen;
            if (bus->dwordmode) {
                bus->nextlen = bus->nextlen << 0x2;
            }
            DHD_TRACE(("Entering %s: length to be read from gSPI = %d\n",
                       __FUNCTION__, bus->nextlen));
        } else {
            if (dstatus & STATUS_F2_PKT_AVAILABLE) {
                DHD_ERROR(("Underflow during %s.\n", __FUNCTION__));
            } else {
                DHD_ERROR(("False pkt-available intr.\n"));
            }
            *finished = TRUE;
            return (maxframes - rxleft);
        }
    }
#endif /* BCMSPI */

    for (rxseq = bus->rx_seq, rxleft = maxframes;
         !bus->rxskip && rxleft && bus->dhd->busstate != DHD_BUS_DOWN;
         rxseq++, rxleft--) {
#ifdef DHDTCPACK_SUP_DBG
        if (bus->dhd->tcpack_sup_mode != TCPACK_SUP_DELAYTX) {
            if (bus->dotxinrx == FALSE) {
                DHD_ERROR(("%s %d: dotxinrx FALSE with tcpack_sub_mode %d\n",
                           __FUNCTION__, __LINE__, bus->dhd->tcpack_sup_mode));
            }
        }
#ifdef DEBUG_COUNTER
        else if (pktq_mlen(&bus->txq, ~bus->flowcontrol) > 0) {
            tack_tbl.cnt[bus->dotxinrx ? 0x6 : 0x7]++;
        }
#endif /* DEBUG_COUNTER */
#endif /* DHDTCPACK_SUP_DBG */
        /* tx more to improve rx performance */
        if (TXCTLOK(bus) && bus->ctrl_frame_stat &&
            (bus->clkstate == CLK_AVAIL)) {
            dhdsdio_sendpendctl(bus);
        } else if (bus->dotxinrx && (bus->clkstate == CLK_AVAIL) &&
                   !bus->fcstate && DATAOK(bus) &&
                   (pktq_mlen(&bus->txq, ~bus->flowcontrol) >
                    bus->txinrx_thres)) {
            dhdsdio_sendfromq(bus, dhd_txbound);
#ifdef DHDTCPACK_SUPPRESS
            /* In TCPACK_SUP_DELAYTX mode, do txinrx only if
             * 1. Any DATA packet to TX
             * 2. TCPACK to TCPDATA PSH packets.
             * in bus txq.
             */
            bus->dotxinrx = (bus->dhd->tcpack_sup_mode == TCPACK_SUP_DELAYTX)
                                ? FALSE
                                : TRUE;
#endif // endif
        }

        /* Handle glomming separately */
        if (bus->glom || bus->glomd) {
            uint8 cnt;
            DHD_GLOM(("%s: calling rxglom: glomd %p, glom %p\n", __FUNCTION__,
                      bus->glomd, bus->glom));
            cnt = dhdsdio_rxglom(bus, rxseq);
            DHD_GLOM(("%s: rxglom returned %d\n", __FUNCTION__, cnt));
            rxseq += cnt - 1;
            rxleft = (rxleft > cnt) ? (rxleft - cnt) : 1;
            continue;
        }

        /* Try doing single read if we can */
        if (dhd_readahead && bus->nextlen) {
            uint16 nextlen = bus->nextlen;
            bus->nextlen = 0;

            if (bus->bus == SPI_BUS) {
                rdlen = len = nextlen;
            } else {
                rdlen = len = nextlen << 0x4;

                /* Pad read to blocksize for efficiency */
                if (bus->roundup && bus->blocksize &&
                    (rdlen > bus->blocksize)) {
                    pad = bus->blocksize - (rdlen % bus->blocksize);
                    if ((pad <= bus->roundup) && (pad < bus->blocksize) &&
                        ((rdlen + pad + firstread) < MAX_RX_DATASZ)) {
                        rdlen += pad;
                    }
                } else if (rdlen % DHD_SDALIGN) {
                    rdlen += DHD_SDALIGN - (rdlen % DHD_SDALIGN);
                }
            }

            /* We use bus->rxctl buffer in WinXP for initial control pkt
             * receives. Later we use buffer-poll for data as well as control
             * packets. This is required because dhd receives full frame in gSPI
             * unlike SDIO. After the frame is received we have to distinguish
             * whether it is data or non-data frame.
             */
            /* Allocate a packet buffer */
            dhd_os_sdlock_rxq(bus->dhd);
            if (!(pkt = PKTGET(osh, rdlen + DHD_SDALIGN, FALSE))) {
                if (bus->bus == SPI_BUS) {
                    bus->usebufpool = FALSE;
                    bus->rxctl = bus->rxbuf;
                    if (dhd_alignctl) {
                        bus->rxctl += firstread;
                        if ((pad = ((uintptr)bus->rxctl % DHD_SDALIGN))) {
                            bus->rxctl += (DHD_SDALIGN - pad);
                        }
                        bus->rxctl -= firstread;
                    }
                    ASSERT(bus->rxctl >= bus->rxbuf);
                    rxbuf = bus->rxctl;
                    /* Read the entire frame */
                    sdret = dhd_bcmsdh_recv_buf(bus, bcmsdh_cur_sbwad(sdh),
                                                SDIO_FUNC_2, F2SYNC, rxbuf,
                                                rdlen, NULL, NULL, NULL);
                    bus->f2rxdata++;
                    ASSERT(sdret != BCME_PENDING);

#ifdef BCMSPI
                    if (bcmsdh_get_dstatus((void *)bus->sdh) &
                        STATUS_UNDERFLOW) {
                        bus->nextlen = 0;
                        *finished = TRUE;
                        DHD_ERROR(("%s: read %d control bytes failed "
                                   "due to spi underflow\n",
                                   __FUNCTION__, rdlen));
                        /* dhd.rx_ctlerrs is higher level */
                        bus->rxc_errors++;
                        dhd_os_sdunlock_rxq(bus->dhd);
                        continue;
                    }
#endif /* BCMSPI */

                    /* Control frame failures need retransmission */
                    if (sdret < 0) {
                        DHD_ERROR(("%s: read %d control bytes failed: %d\n",
                                   __FUNCTION__, rdlen, sdret));
                        /* dhd.rx_ctlerrs is higher level */
                        bus->rxc_errors++;
                        dhd_os_sdunlock_rxq(bus->dhd);
                        dhdsdio_rxfail(bus, TRUE,
                                       (bus->bus == SPI_BUS) ? FALSE : TRUE);
                        continue;
                    }
                } else {
                    /* Give up on data, request rtx of events */
                    DHD_ERROR(("%s (nextlen): PKTGET failed: len %d rdlen %d "
                               "expected rxseq %d\n",
                               __FUNCTION__, len, rdlen, rxseq));
                    /* Just go try again w/normal header read */
                    dhd_os_sdunlock_rxq(bus->dhd);
                    continue;
                }
            } else {
                if (bus->bus == SPI_BUS) {
                    bus->usebufpool = TRUE;
                }

                ASSERT(!PKTLINK(pkt));
                PKTALIGN(osh, pkt, rdlen, DHD_SDALIGN);
                rxbuf = (uint8 *)PKTDATA(osh, pkt);
                /* Read the entire frame */
                sdret =
                    dhd_bcmsdh_recv_buf(bus, bcmsdh_cur_sbwad(sdh), SDIO_FUNC_2,
                                        F2SYNC, rxbuf, rdlen, pkt, NULL, NULL);
                bus->f2rxdata++;
                ASSERT(sdret != BCME_PENDING);
#ifdef BCMSPI
                if (bcmsdh_get_dstatus((void *)bus->sdh) & STATUS_UNDERFLOW) {
                    bus->nextlen = 0;
                    *finished = TRUE;
                    DHD_ERROR(("%s (nextlen): read %d bytes failed due "
                               "to spi underflow\n",
                               __FUNCTION__, rdlen));
                    PKTFREE(bus->dhd->osh, pkt, FALSE);
                    bus->dhd->rx_errors++;
                    dhd_os_sdunlock_rxq(bus->dhd);
                    continue;
                }
#endif /* BCMSPI */

                if (sdret < 0) {
                    DHD_ERROR(("%s (nextlen): read %d bytes failed: %d\n",
                               __FUNCTION__, rdlen, sdret));
                    PKTFREE(bus->dhd->osh, pkt, FALSE);
                    bus->dhd->rx_errors++;
                    dhd_os_sdunlock_rxq(bus->dhd);
                    /* Force retry w/normal header read.  Don't attempt NAK for
                     * gSPI
                     */
                    dhdsdio_rxfail(bus, TRUE,
                                   (bus->bus == SPI_BUS) ? FALSE : TRUE);
                    continue;
                }
            }
            dhd_os_sdunlock_rxq(bus->dhd);

            /* Now check the header */
            bcopy(rxbuf, bus->rxhdr, SDPCM_HDRLEN);

            /* Extract hardware header fields */
            len = ltoh16_ua(bus->rxhdr);
            check = ltoh16_ua(bus->rxhdr + sizeof(uint16));
            /* All zeros means readahead info was bad */
            if (!(len | check)) {
                DHD_INFO(("%s (nextlen): read zeros in HW header???\n",
                          __FUNCTION__));
                dhd_os_sdlock_rxq(bus->dhd);
                PKTFREE2();
                dhd_os_sdunlock_rxq(bus->dhd);
                GSPI_PR55150_BAILOUT;
                continue;
            }

            /* Validate check bytes */
            if ((uint16) ~(len ^ check)) {
                DHD_ERROR(("%s (nextlen): HW hdr error: nextlen/len/check"
                           " 0x%04x/0x%04x/0x%04x\n",
                           __FUNCTION__, nextlen, len, check));
                dhd_os_sdlock_rxq(bus->dhd);
                PKTFREE2();
                dhd_os_sdunlock_rxq(bus->dhd);
                bus->rx_badhdr++;
                dhdsdio_rxfail(bus, FALSE, FALSE);
                GSPI_PR55150_BAILOUT;
                continue;
            }

            /* Validate frame length */
            if (len < SDPCM_HDRLEN) {
                DHD_ERROR(("%s (nextlen): HW hdr length invalid: %d\n",
                           __FUNCTION__, len));
                dhd_os_sdlock_rxq(bus->dhd);
                PKTFREE2();
                dhd_os_sdunlock_rxq(bus->dhd);
                GSPI_PR55150_BAILOUT;
                continue;
            }

            /* Check for consistency with readahead info */
#ifdef BCMSPI
            if (bus->bus == SPI_BUS) {
                if (bus->dwordmode) {
                    uint16 spilen;
                    spilen = ROUNDUP(len, 0x4);
                    len_consistent = (nextlen != spilen);
                } else {
                    len_consistent = (nextlen != len);
                }
            } else
#endif /* BCMSPI */
                len_consistent = (nextlen != (ROUNDUP(len, 0x10) >> 0x4));
            if (len_consistent) {
                /* Mismatch, force retry w/normal header (may be >4K) */
                DHD_ERROR(("%s (nextlen): mismatch, nextlen %d len %d rnd %d; "
                           "expected rxseq %d\n",
                           __FUNCTION__, nextlen, len, ROUNDUP(len, 0x10),
                           rxseq));
                dhd_os_sdlock_rxq(bus->dhd);
                PKTFREE2();
                dhd_os_sdunlock_rxq(bus->dhd);
                dhdsdio_rxfail(bus, TRUE, (bus->bus == SPI_BUS) ? FALSE : TRUE);
                GSPI_PR55150_BAILOUT;
                continue;
            }

            /* Extract software header fields */
            chan = SDPCM_PACKET_CHANNEL(&bus->rxhdr[SDPCM_FRAMETAG_LEN]);
            seq = SDPCM_PACKET_SEQUENCE(&bus->rxhdr[SDPCM_FRAMETAG_LEN]);
            doff = SDPCM_DOFFSET_VALUE(&bus->rxhdr[SDPCM_FRAMETAG_LEN]);
            txmax = SDPCM_WINDOW_VALUE(&bus->rxhdr[SDPCM_FRAMETAG_LEN]);

#ifdef BCMSPI
            /* Save the readahead length if there is one */
            if (bus->bus == SPI_BUS) {
                /* Use reconstructed dstatus bits and find out readahead size */
                dstatus = bcmsdh_get_dstatus((void *)bus->sdh);
                DHD_INFO(("Device status from bit-reconstruction = 0x%x\n",
                          bcmsdh_get_dstatus((void *)bus->sdh)));
                if (dstatus & STATUS_F2_PKT_AVAILABLE) {
                    bus->nextlen = ((dstatus & STATUS_F2_PKT_LEN_MASK) >>
                                    STATUS_F2_PKT_LEN_SHIFT);
                    bus->nextlen =
                        (bus->nextlen == 0) ? SPI_MAX_PKT_LEN : bus->nextlen;
                    if (bus->dwordmode) {
                        bus->nextlen = bus->nextlen << 0x2;
                    }
                    DHD_INFO(("readahead len from gSPI = %d \n", bus->nextlen));
                    bus->dhd->rx_readahead_cnt++;
                } else {
                    bus->nextlen = 0;
                    *finished = TRUE;
                }
            } else {
#endif /* BCMSPI */
                bus->nextlen =
                    bus->rxhdr[SDPCM_FRAMETAG_LEN + SDPCM_NEXTLEN_OFFSET];
                if ((bus->nextlen << 0x4) > MAX_RX_DATASZ) {
                    DHD_INFO(("%s (nextlen): got frame w/nextlen too large"
                              " (%d), seq %d\n",
                              __FUNCTION__, bus->nextlen, seq));
                    bus->nextlen = 0;
                }

                bus->dhd->rx_readahead_cnt++;
#ifdef BCMSPI
            }
#endif /* BCMSPI */
            /* Handle Flow Control */
            fcbits = SDPCM_FCMASK_VALUE(&bus->rxhdr[SDPCM_FRAMETAG_LEN]);

            delta = 0;
            if (~bus->flowcontrol & fcbits) {
                bus->fc_xoff++;
                delta = 1;
            }
            if (bus->flowcontrol & ~fcbits) {
                bus->fc_xon++;
                delta = 1;
            }

            if (delta) {
                bus->fc_rcvd++;
                bus->flowcontrol = fcbits;
            }

            /* Check and update sequence number */
            if (rxseq != seq) {
                DHD_INFO(("%s (nextlen): rx_seq %d, expected %d\n",
                          __FUNCTION__, seq, rxseq));
                bus->rx_badseq++;
                rxseq = seq;
            }

            /* Check window for sanity */
            if ((uint8)(txmax - bus->tx_seq) > 0x70) {
#ifdef BCMSPI
                if ((bus->bus == SPI_BUS) && !(dstatus & STATUS_F2_RX_READY)) {
                    DHD_INFO(("%s: got unlikely tx max %d with tx_seq %d\n",
                              __FUNCTION__, txmax, bus->tx_seq));
                    txmax = bus->tx_seq + 0x2;
                } else {
#endif /* BCMSPI */
                    DHD_INFO(("%s: got unlikely tx max %d with tx_seq %d\n",
                              __FUNCTION__, txmax, bus->tx_seq));
                    txmax = bus->tx_max;
#ifdef BCMSPI
                }
#endif /* BCMSPI */
            }
            bus->tx_max = txmax;

#ifdef DHD_DEBUG
            if (DHD_BYTES_ON() && DHD_DATA_ON()) {
                prhex("Rx Data", rxbuf, len);
            } else if (DHD_HDRS_ON()) {
                prhex("RxHdr", bus->rxhdr, SDPCM_HDRLEN);
            }
#endif // endif

            if (chan == SDPCM_CONTROL_CHANNEL) {
                if (bus->bus == SPI_BUS) {
                    dhdsdio_read_control(bus, rxbuf, len, doff);
                    if (bus->usebufpool) {
                        dhd_os_sdlock_rxq(bus->dhd);
                        PKTFREE(bus->dhd->osh, pkt, FALSE);
                        dhd_os_sdunlock_rxq(bus->dhd);
                    }
                    continue;
                } else {
                    DHD_ERROR(("%s (nextlen): readahead on control"
                               " packet %d?\n",
                               __FUNCTION__, seq));
                    /* Force retry w/normal header read */
                    bus->nextlen = 0;
                    dhdsdio_rxfail(bus, FALSE, TRUE);
                    dhd_os_sdlock_rxq(bus->dhd);
                    PKTFREE2();
                    dhd_os_sdunlock_rxq(bus->dhd);
                    continue;
                }
            }

            if ((bus->bus == SPI_BUS) && !bus->usebufpool) {
                DHD_ERROR(("Received %d bytes on %d channel. Running out of "
                           "rx pktbuf's or not yet malloced.\n",
                           len, chan));
                continue;
            }

            /* Validate data offset */
            if ((doff < SDPCM_HDRLEN) || (doff > len)) {
                DHD_ERROR(
                    ("%s (nextlen): bad data offset %d: HW len %d min %d\n",
                     __FUNCTION__, doff, len, SDPCM_HDRLEN));
                dhd_os_sdlock_rxq(bus->dhd);
                PKTFREE2();
                dhd_os_sdunlock_rxq(bus->dhd);
                ASSERT(0);
                dhdsdio_rxfail(bus, FALSE, FALSE);
                continue;
            }

            /* All done with this one -- now deliver the packet */
            goto deliver;
        }
        /* gSPI frames should not be handled in fractions */
        if (bus->bus == SPI_BUS) {
            break;
        }

        /* Read frame header (hardware and software) */
        sdret =
            dhd_bcmsdh_recv_buf(bus, bcmsdh_cur_sbwad(sdh), SDIO_FUNC_2, F2SYNC,
                                bus->rxhdr, firstread, NULL, NULL, NULL);
        bus->f2rxhdrs++;
        ASSERT(sdret != BCME_PENDING);

        if (sdret < 0) {
            DHD_ERROR(("%s: RXHEADER FAILED: %d\n", __FUNCTION__, sdret));
            bus->rx_hdrfail++;
            dhdsdio_rxfail(bus, TRUE, TRUE);
            continue;
        }

#ifdef DHD_DEBUG
        if (DHD_BYTES_ON() || DHD_HDRS_ON()) {
            prhex("RxHdr", bus->rxhdr, SDPCM_HDRLEN);
        }
#endif // endif

        /* Extract hardware header fields */
        len = ltoh16_ua(bus->rxhdr);
        check = ltoh16_ua(bus->rxhdr + sizeof(uint16));
        /* All zeros means no more frames */
        if (!(len | check)) {
            *finished = TRUE;
            break;
        }
        /* Validate check bytes */
        if ((uint16) ~(len ^ check)) {
            DHD_ERROR(("%s: HW hdr error: len/check 0x%04x/0x%04x\n",
                       __FUNCTION__, len, check));
            bus->rx_badhdr++;
            dhdsdio_rxfail(bus, FALSE, FALSE);
            continue;
        }
        /* Validate frame length */
        if (len < SDPCM_HDRLEN) {
            DHD_ERROR(("%s: HW hdr length invalid: %d\n", __FUNCTION__, len));
            continue;
        }

        /* Extract software header fields */
        chan = SDPCM_PACKET_CHANNEL(&bus->rxhdr[SDPCM_FRAMETAG_LEN]);
        seq = SDPCM_PACKET_SEQUENCE(&bus->rxhdr[SDPCM_FRAMETAG_LEN]);
        doff = SDPCM_DOFFSET_VALUE(&bus->rxhdr[SDPCM_FRAMETAG_LEN]);
        txmax = SDPCM_WINDOW_VALUE(&bus->rxhdr[SDPCM_FRAMETAG_LEN]);

        /* Validate data offset */
        if ((doff < SDPCM_HDRLEN) || (doff > len)) {
            DHD_ERROR(("%s: Bad data offset %d: HW len %d, min %d seq %d\n",
                       __FUNCTION__, doff, len, SDPCM_HDRLEN, seq));
            bus->rx_badhdr++;
            ASSERT(0);
            dhdsdio_rxfail(bus, FALSE, FALSE);
            continue;
        }

        /* Save the readahead length if there is one */
        bus->nextlen = bus->rxhdr[SDPCM_FRAMETAG_LEN + SDPCM_NEXTLEN_OFFSET];
        if ((bus->nextlen << 0x4) > MAX_RX_DATASZ) {
            DHD_INFO(
                ("%s (nextlen): got frame w/nextlen too large (%d), seq %d\n",
                 __FUNCTION__, bus->nextlen, seq));
            bus->nextlen = 0;
        }

        /* Handle Flow Control */
        fcbits = SDPCM_FCMASK_VALUE(&bus->rxhdr[SDPCM_FRAMETAG_LEN]);

        delta = 0;
        if (~bus->flowcontrol & fcbits) {
            bus->fc_xoff++;
            delta = 1;
        }
        if (bus->flowcontrol & ~fcbits) {
            bus->fc_xon++;
            delta = 1;
        }

        if (delta) {
            bus->fc_rcvd++;
            bus->flowcontrol = fcbits;
        }

        /* Check and update sequence number */
        if (rxseq != seq) {
            DHD_INFO(
                ("%s: rx_seq %d, expected %d\n", __FUNCTION__, seq, rxseq));
            bus->rx_badseq++;
            rxseq = seq;
        }

        /* Check window for sanity */
        if ((uint8)(txmax - bus->tx_seq) > 0x70) {
            DHD_INFO(("%s: got unlikely tx max %d with tx_seq %d\n",
                      __FUNCTION__, txmax, bus->tx_seq));
            txmax = bus->tx_max;
        }
        bus->tx_max = txmax;

        /* Call a separate function for control frames */
        if (chan == SDPCM_CONTROL_CHANNEL) {
            dhdsdio_read_control(bus, bus->rxhdr, len, doff);
            continue;
        }

        ASSERT((chan == SDPCM_DATA_CHANNEL) || (chan == SDPCM_EVENT_CHANNEL) ||
               (chan == SDPCM_TEST_CHANNEL) || (chan == SDPCM_GLOM_CHANNEL));

        /* Length to read */
        rdlen = (len > firstread) ? (len - firstread) : 0;

        /* May pad read to blocksize for efficiency */
        if (bus->roundup && bus->blocksize && (rdlen > bus->blocksize)) {
            pad = bus->blocksize - (rdlen % bus->blocksize);
            if ((pad <= bus->roundup) && (pad < bus->blocksize) &&
                ((rdlen + pad + firstread) < MAX_RX_DATASZ)) {
                rdlen += pad;
            }
        } else if (rdlen % DHD_SDALIGN) {
            rdlen += DHD_SDALIGN - (rdlen % DHD_SDALIGN);
        }

        /* Satisfy length-alignment requirements */
        if (forcealign && (rdlen & (ALIGNMENT - 1))) {
            rdlen = ROUNDUP(rdlen, ALIGNMENT);
        }

        if ((rdlen + firstread) > MAX_RX_DATASZ) {
            /* Too long -- skip this frame */
            DHD_ERROR(
                ("%s: too long: len %d rdlen %d\n", __FUNCTION__, len, rdlen));
            bus->dhd->rx_errors++;
            bus->rx_toolong++;
            dhdsdio_rxfail(bus, FALSE, FALSE);
            continue;
        }

        dhd_os_sdlock_rxq(bus->dhd);
        if (!(pkt = PKTGET(osh, (rdlen + firstread + DHD_SDALIGN), FALSE))) {
            /* Give up on data, request rtx of events */
            DHD_ERROR(("%s: PKTGET failed: rdlen %d chan %d\n", __FUNCTION__,
                       rdlen, chan));
            bus->dhd->rx_dropped++;
            dhd_os_sdunlock_rxq(bus->dhd);
            dhdsdio_rxfail(bus, FALSE, RETRYCHAN(chan));
            continue;
        }
        dhd_os_sdunlock_rxq(bus->dhd);

        ASSERT(!PKTLINK(pkt));

        /* Leave room for what we already read, and align remainder */
        ASSERT(firstread < (PKTLEN(osh, pkt)));
        PKTPULL(osh, pkt, firstread);
        PKTALIGN(osh, pkt, rdlen, DHD_SDALIGN);

        /* Read the remaining frame data */
        sdret = dhd_bcmsdh_recv_buf(bus, bcmsdh_cur_sbwad(sdh), SDIO_FUNC_2,
                                    F2SYNC, ((uint8 *)PKTDATA(osh, pkt)), rdlen,
                                    pkt, NULL, NULL);
        bus->f2rxdata++;
        ASSERT(sdret != BCME_PENDING);

        if (sdret < 0) {
            DHD_ERROR(("%s: read %d %s bytes failed: %d\n", __FUNCTION__, rdlen,
                       ((chan == SDPCM_EVENT_CHANNEL)
                            ? "event"
                            : ((chan == SDPCM_DATA_CHANNEL) ? "data" : "test")),
                       sdret));
            dhd_os_sdlock_rxq(bus->dhd);
            PKTFREE(bus->dhd->osh, pkt, FALSE);
            dhd_os_sdunlock_rxq(bus->dhd);
            bus->dhd->rx_errors++;
            dhdsdio_rxfail(bus, TRUE, RETRYCHAN(chan));
            continue;
        }

        /* Copy the already-read portion */
        PKTPUSH(osh, pkt, firstread);
        bcopy(bus->rxhdr, PKTDATA(osh, pkt), firstread);

#ifdef DHD_DEBUG
        if (DHD_BYTES_ON() && DHD_DATA_ON()) {
            prhex("Rx Data", PKTDATA(osh, pkt), len);
        }
#endif // endif

    deliver:
        /* Save superframe descriptor and allocate packet frame */
        if (chan == SDPCM_GLOM_CHANNEL) {
            if (SDPCM_GLOMDESC(&bus->rxhdr[SDPCM_FRAMETAG_LEN])) {
                DHD_GLOM(("%s: got glom descriptor, %d bytes:\n", __FUNCTION__,
                          len));
#ifdef DHD_DEBUG
                if (DHD_GLOM_ON()) {
                    prhex("Glom Data", PKTDATA(osh, pkt), len);
                }
#endif // endif
                PKTSETLEN(osh, pkt, len);
                ASSERT(doff == SDPCM_HDRLEN);
                PKTPULL(osh, pkt, SDPCM_HDRLEN);
                bus->glomd = pkt;
            } else {
                DHD_ERROR(
                    ("%s: glom superframe w/o descriptor!\n", __FUNCTION__));
                dhdsdio_rxfail(bus, FALSE, FALSE);
            }
            continue;
        }

        /* Fill in packet len and prio, deliver upward */
        PKTSETLEN(osh, pkt, len);
        PKTPULL(osh, pkt, doff);

#ifdef SDTEST
        /* Test channel packets are processed separately */
        if (chan == SDPCM_TEST_CHANNEL) {
            dhdsdio_testrcv(bus, pkt, seq);
            continue;
        }
#endif /* SDTEST */

        if (PKTLEN(osh, pkt) == 0) {
            dhd_os_sdlock_rxq(bus->dhd);
            PKTFREE(bus->dhd->osh, pkt, FALSE);
            dhd_os_sdunlock_rxq(bus->dhd);
            continue;
        } else if (dhd_prot_hdrpull(bus->dhd, &ifidx, pkt, reorder_info_buf,
                                    &reorder_info_len) != 0) {
            DHD_ERROR(("%s: rx protocol error\n", __FUNCTION__));
            dhd_os_sdlock_rxq(bus->dhd);
            PKTFREE(bus->dhd->osh, pkt, FALSE);
            dhd_os_sdunlock_rxq(bus->dhd);
            bus->dhd->rx_errors++;
            continue;
        }

        if (reorder_info_len) {
            /* Reordering info from the firmware */
            dhd_process_pkt_reorder_info(bus->dhd, reorder_info_buf,
                                         reorder_info_len, &pkt, &pkt_count);
            if (pkt_count == 0) {
                continue;
            }
        } else {
            pkt_count = 1;
        }

        /* Unlock during rx call */
        dhd_os_sdunlock(bus->dhd);
        dhd_rx_frame(bus->dhd, ifidx, pkt, pkt_count, chan);
        dhd_os_sdlock(bus->dhd);
#if defined(SDIO_ISR_THREAD)
        /* terence 20150615: fix for below error due to bussleep in watchdog
         * after dhd_os_sdunlock here, so call BUS_WAKE to wake up bus again
         * dhd_bcmsdh_recv_buf: Device asleep
         * dhdsdio_readframes: RXHEADER FAILED: -40
         * dhdsdio_rxfail: abort command, terminate frame, send NAK
         */
        BUS_WAKE(bus);
#endif
    }
    rxcount = maxframes - rxleft;
#ifdef DHD_DEBUG
    /* Message if we hit the limit */
    if (!rxleft && !sdtest) {
        DHD_DATA(("%s: hit rx limit of %d frames\n", __FUNCTION__, maxframes));
    } else
#endif /* DHD_DEBUG */
        DHD_DATA(("%s: processed %d frames\n", __FUNCTION__, rxcount));
    /* Back off rxseq if awaiting rtx, update rx_seq */
    if (bus->rxskip) {
        rxseq--;
    }
    bus->rx_seq = rxseq;

    if (bus->reqbussleep) {
        dhdsdio_bussleep(bus, TRUE);
        bus->reqbussleep = FALSE;
    }
    bus->readframes = FALSE;

    return rxcount;
}

static uint32 dhdsdio_hostmail(dhd_bus_t *bus, uint32 *hmbd)
{
    sdpcmd_regs_t *regs = bus->regs;
    uint32 intstatus = 0;
    uint32 hmb_data;
    uint8 fcbits;
    uint retries = 0;

    DHD_TRACE(("%s: Enter\n", __FUNCTION__));

    /* Read mailbox data and ack that we did so */
    R_SDREG(hmb_data, &regs->tohostmailboxdata, retries);
    if (retries <= retry_limit) {
        W_SDREG(SMB_INT_ACK, &regs->tosbmailbox, retries);
    }
    bus->f1regdata += 0x2;

    /* Dongle recomposed rx frames, accept them again */
    if (hmb_data & HMB_DATA_NAKHANDLED) {
        DHD_INFO(
            ("Dongle reports NAK handled, expect rtx of %d\n", bus->rx_seq));
        if (!bus->rxskip) {
            DHD_ERROR(("%s: unexpected NAKHANDLED!\n", __FUNCTION__));
        }
        bus->rxskip = FALSE;
        intstatus |= FRAME_AVAIL_MASK(bus);
    }

    /*
     * DEVREADY does not occur with gSPI.
     */
    if (hmb_data & (HMB_DATA_DEVREADY | HMB_DATA_FWREADY)) {
        bus->sdpcm_ver =
            (hmb_data & HMB_DATA_VERSION_MASK) >> HMB_DATA_VERSION_SHIFT;
        if (bus->sdpcm_ver != SDPCM_PROT_VERSION) {
            DHD_ERROR(("Version mismatch, dongle reports %d, expecting %d\n",
                       bus->sdpcm_ver, SDPCM_PROT_VERSION));
        } else {
            DHD_INFO(("Dongle ready, protocol version %d\n", bus->sdpcm_ver));
        }
#ifndef BCMSPI
        /* make sure for the SDIO_DEVICE_RXDATAINT_MODE_1 corecontrol is proper
         */
        if ((bus->sih->buscoretype == SDIOD_CORE_ID) && (bus->sdpcmrev >= 0x4) &&
            (bus->rxint_mode == SDIO_DEVICE_RXDATAINT_MODE_1)) {
            uint32 val;

            val = R_REG(bus->dhd->osh, &bus->regs->corecontrol);
            val &= ~CC_XMTDATAAVAIL_MODE;
            val |= CC_XMTDATAAVAIL_CTRL;
            W_REG(bus->dhd->osh, &bus->regs->corecontrol, val);

            val = R_REG(bus->dhd->osh, &bus->regs->corecontrol);
        }
#endif /* BCMSPI */

#ifdef DHD_DEBUG
        /* Retrieve console state address now that firmware should have updated
         * it */
        {
            sdpcm_shared_t shared;
            if (dhdsdio_readshared(bus, &shared) == 0) {
                bus->console_addr = shared.console_addr;
            }
        }
#endif /* DHD_DEBUG */
    }

    /*
     * Flow Control has been moved into the RX headers and this out of band
     * method isn't used any more.  Leave this here for possibly remaining
     * backward compatible with older dongles
     */
    if (hmb_data & HMB_DATA_FC) {
        fcbits = (hmb_data & HMB_DATA_FCDATA_MASK) >> HMB_DATA_FCDATA_SHIFT;

        if (fcbits & ~bus->flowcontrol) {
            bus->fc_xoff++;
        }
        if (bus->flowcontrol & ~fcbits) {
            bus->fc_xon++;
        }

        bus->fc_rcvd++;
        bus->flowcontrol = fcbits;
    }

    /* At least print a message if FW halted */
    if (hmb_data & HMB_DATA_FWHALT) {
        DHD_ERROR(("INTERNAL ERROR: FIRMWARE HALTED : set BUS DOWN\n"));
        dhdsdio_checkdied(bus, NULL, 0);
        DHD_ERROR(("Not doing bus down untill memdump done \n"));
    }

    /* Shouldn't be any others */
    if (hmb_data & ~(HMB_DATA_DEVREADY | HMB_DATA_FWHALT | HMB_DATA_NAKHANDLED |
                     HMB_DATA_FC | HMB_DATA_FWREADY | HMB_DATA_FCDATA_MASK |
                     HMB_DATA_VERSION_MASK)) {
        DHD_ERROR(("Unknown mailbox data content: 0x%02x\n", hmb_data));
    }

    if (hmbd) {
        *hmbd = hmb_data;
    }

    return intstatus;
}

#ifdef BCMSDIO_INTSTATUS_WAR
static uint32 dhdsdio_read_intstatus_byte(dhd_bus_t *bus)
{
    bcmsdh_info_t *sdh = bus->sdh;
    sdpcmd_regs_t *regs = bus->regs;
    uint32 newstatus = 0, intstatus_byte = 0;
    uint retries = 0;
    int err1 = 0, err2 = 0, err3 = 0, err4 = 0;

    DHD_TRACE(("%s: Enter\n", __FUNCTION__));

    /* read_intr_mode:
     * 0: word mode only (default)
     * 1: byte mode after read word failed
     * 2: byte mode only
     */
    if (bus->dhd->conf->read_intr_mode) {
        if (bus->dhd->conf->read_intr_mode == 1) {
            R_SDREG(newstatus, &regs->intstatus, retries);
            if (!bcmsdh_regfail(bus->sdh)) {
                goto exit;
            }
        }
        intstatus_byte = bcmsdh_cfg_read(
            bus->sdh, SDIO_FUNC_1,
            ((unsigned long)&regs->intstatus & 0xffff) + 0, &err1);
        if (!err1) {
            newstatus |= intstatus_byte;
        }
        intstatus_byte =
            bcmsdh_cfg_read(bus->sdh, SDIO_FUNC_1,
                            ((unsigned long)&regs->intstatus & 0xffff) + 1,
                            &err2)
            << 0x8;
        if (!err2) {
            newstatus |= intstatus_byte;
        }
        intstatus_byte |=
            bcmsdh_cfg_read(bus->sdh, SDIO_FUNC_1,
                            ((unsigned long)&regs->intstatus & 0xffff) + 0x2,
                            &err3)
            << 0x10;
        if (!err3) {
            newstatus |= intstatus_byte;
        }
        intstatus_byte |=
            bcmsdh_cfg_read(bus->sdh, SDIO_FUNC_1,
                            ((unsigned long)&regs->intstatus & 0xffff) + 0x3,
                            &err4)
            << 0x18;
        if (!err4) {
            newstatus |= intstatus_byte;
        }

        if (!err1 || !err2 || !err3 || !err4) {
            sdh->regfail = FALSE;
        }
    } else {
        R_SDREG(newstatus, &regs->intstatus, retries);
    }

exit:
    return newstatus;
}
#endif

static bool dhdsdio_dpc(dhd_bus_t *bus)
{
    bcmsdh_info_t *sdh = bus->sdh;
    sdpcmd_regs_t *regs = bus->regs;
    uint32 intstatus, newstatus = 0;
    uint retries = 0;
    uint rxlimit = dhd_rxbound; /* Rx frames to read before resched */
    uint txlimit = dhd_txbound; /* Tx frames to send before resched */
    uint framecnt = 0;          /* Temporary counter of tx/rx frames */
    bool rxdone = TRUE;         /* Flag for no more read data */
    bool resched = FALSE;       /* Flag indicating resched wanted */
    unsigned long flags;
#ifdef DEBUG_DPC_THREAD_WATCHDOG
    bool is_resched_by_readframe = FALSE;
#endif /* DEBUG_DPC_THREAD_WATCHDOG */
    DHD_TRACE(("%s: Enter\n", __FUNCTION__));

    dhd_os_sdlock(bus->dhd);
    DHD_LINUX_GENERAL_LOCK(bus->dhd, flags);
    if (bus->dhd->busstate == DHD_BUS_DOWN) {
        DHD_ERROR(("%s: Bus down, ret\n", __FUNCTION__));
        bus->intstatus = 0;
        DHD_LINUX_GENERAL_UNLOCK(bus->dhd, flags);
        dhd_os_sdunlock(bus->dhd);
        return 0;
    }

    DHD_BUS_BUSY_SET_IN_DPC(bus->dhd);
    DHD_LINUX_GENERAL_UNLOCK(bus->dhd, flags);

    /* Start with leftover status bits */
    intstatus = bus->intstatus;

    if (!SLPAUTO_ENAB(bus) && !KSO_ENAB(bus)) {
        DHD_ERROR(("%s: Device asleep\n", __FUNCTION__));
        goto exit;
    }

    /* If waiting for HTAVAIL, check status */
    if (!SLPAUTO_ENAB(bus) && (bus->clkstate == CLK_PENDING)) {
        int err;
        uint8 clkctl, devctl = 0;

#ifdef DHD_DEBUG
        /* Check for inconsistent device control */
        devctl = bcmsdh_cfg_read(sdh, SDIO_FUNC_1, SBSDIO_DEVICE_CTL, &err);
        if (err) {
            DHD_ERROR(("%s: error reading DEVCTL: %d\n", __FUNCTION__, err));
            DHD_ERROR(("%s: making DHD_BUS_DOWN\n", __FUNCTION__));
            bus->dhd->busstate = DHD_BUS_DOWN;
        } else {
            ASSERT(devctl & SBSDIO_DEVCTL_CA_INT_ONLY);
        }
#endif /* DHD_DEBUG */

        /* Read CSR, if clock on switch to AVAIL, else ignore */
        clkctl =
            bcmsdh_cfg_read(sdh, SDIO_FUNC_1, SBSDIO_FUNC1_CHIPCLKCSR, &err);
        if (err) {
            DHD_ERROR(("%s: error reading CSR: %d\n", __FUNCTION__, err));
            DHD_ERROR(("%s: making DHD_BUS_DOWN\n", __FUNCTION__));
            bus->dhd->busstate = DHD_BUS_DOWN;
        }

        DHD_INFO(
            ("DPC: PENDING, devctl 0x%02x clkctl 0x%02x\n", devctl, clkctl));

        if (SBSDIO_HTAV(clkctl)) {
            devctl = bcmsdh_cfg_read(sdh, SDIO_FUNC_1, SBSDIO_DEVICE_CTL, &err);
            if (err) {
                DHD_ERROR(
                    ("%s: error reading DEVCTL: %d\n", __FUNCTION__, err));
                DHD_ERROR(("%s: making DHD_BUS_DOWN\n", __FUNCTION__));
                bus->dhd->busstate = DHD_BUS_DOWN;
            }
            devctl &= ~SBSDIO_DEVCTL_CA_INT_ONLY;
            bcmsdh_cfg_write(sdh, SDIO_FUNC_1, SBSDIO_DEVICE_CTL, devctl, &err);
            if (err) {
                DHD_ERROR(
                    ("%s: error writing DEVCTL: %d\n", __FUNCTION__, err));
                DHD_ERROR(("%s: making DHD_BUS_DOWN\n", __FUNCTION__));
                bus->dhd->busstate = DHD_BUS_DOWN;
            }
            bus->clkstate = CLK_AVAIL;
        } else {
            goto clkwait;
        }
    }

    BUS_WAKE(bus);

    /* Make sure backplane clock is on */
    dhdsdio_clkctl(bus, CLK_AVAIL, TRUE);
    if (bus->clkstate != CLK_AVAIL) {
        goto clkwait;
    }

    /* Pending interrupt indicates new device status */
    if (bus->ipend) {
        bus->ipend = FALSE;
#if defined(BT_OVER_SDIO)
        bcmsdh_btsdio_process_f3_intr();
#endif /* defined (BT_OVER_SDIO) */

#ifdef BCMSDIO_INTSTATUS_WAR
        newstatus = dhdsdio_read_intstatus_byte(bus);
#else
        R_SDREG(newstatus, &regs->intstatus, retries);
#endif
        bus->f1regdata++;
        if (bcmsdh_regfail(bus->sdh)) {
            newstatus = 0;
        }
        newstatus &= bus->hostintmask;
        bus->fcstate = !!(newstatus & I_HMB_FC_STATE);
        if (newstatus) {
            bus->f1regdata++;
#ifndef BCMSPI
            if ((bus->rxint_mode == SDIO_DEVICE_RXDATAINT_MODE_0) &&
                (newstatus == I_XMTDATA_AVAIL)) {
            } else
#endif /* BCMSPI */
                W_SDREG(newstatus, &regs->intstatus, retries);
        }
    }

    /* Merge new bits with previous */
    intstatus |= newstatus;
    bus->intstatus = 0;

    /* Handle flow-control change: read new state in case our ack
     * crossed another change interrupt.  If change still set, assume
     * FC ON for safety, let next loop through do the debounce.
     */
    if (intstatus & I_HMB_FC_CHANGE) {
        intstatus &= ~I_HMB_FC_CHANGE;
        W_SDREG(I_HMB_FC_CHANGE, &regs->intstatus, retries);
        R_SDREG(newstatus, &regs->intstatus, retries);
        bus->f1regdata += 0x2;
        bus->fcstate = !!(newstatus & (I_HMB_FC_STATE | I_HMB_FC_CHANGE));
        intstatus |= (newstatus & bus->hostintmask);
    }

    /* Handle host mailbox indication */
    if (intstatus & I_HMB_HOST_INT) {
        uint32 hmbdata = 0;

        intstatus &= ~I_HMB_HOST_INT;
        intstatus |= dhdsdio_hostmail(bus, &hmbdata);

#ifdef DHD_ULP
        /* ULP prototyping. Redowload fw on oob interupt */

        /* all the writes after this point CAN use cached sbwad value */
        bcmsdh_force_sbwad_calc(bus->sdh, FALSE);

        if (dhd_ulp_pre_redownload_check(bus->dhd, bus->sdh, hmbdata)) {
            if (dhd_bus_ulp_reinit_fw(bus) < 0) {
                DHD_ERROR(
                    ("%s:%d FW redownload failed\n", __FUNCTION__, __LINE__));
                goto exit;
            }
        }
#endif // endif
    }

#ifdef DHD_UCODE_DOWNLOAD
exit_ucode:
#endif /* DHD_UCODE_DOWNLOAD */

    /* Just being here means nothing more to do for chipactive */
    if (intstatus & I_CHIPACTIVE) {
        intstatus &= ~I_CHIPACTIVE;
    }

    /* Handle host mailbox indication */
    if (intstatus & I_HMB_HOST_INT) {
        intstatus &= ~I_HMB_HOST_INT;
        intstatus |= dhdsdio_hostmail(bus, NULL);
    }

    /* Generally don't ask for these, can get CRC errors... */
    if (intstatus & I_WR_OOSYNC) {
        DHD_ERROR(("Dongle reports WR_OOSYNC\n"));
        intstatus &= ~I_WR_OOSYNC;
    }

    if (intstatus & I_RD_OOSYNC) {
        DHD_ERROR(("Dongle reports RD_OOSYNC\n"));
        intstatus &= ~I_RD_OOSYNC;
    }

    if (intstatus & I_SBINT) {
        DHD_ERROR(("Dongle reports SBINT\n"));
        intstatus &= ~I_SBINT;
    }

    /* Would be active due to wake-wlan in gSPI */
    if (intstatus & I_CHIPACTIVE) {
        DHD_INFO(("Dongle reports CHIPACTIVE\n"));
        intstatus &= ~I_CHIPACTIVE;
    }

    if (intstatus & I_HMB_FC_STATE) {
        DHD_INFO(("Dongle reports HMB_FC_STATE\n"));
        intstatus &= ~I_HMB_FC_STATE;
    }

    /* Ignore frame indications if rxskip is set */
    if (bus->rxskip) {
        intstatus &= ~FRAME_AVAIL_MASK(bus);
    }

    /* On frame indication, read available frames */
    if (PKT_AVAILABLE(bus, intstatus)) {
        framecnt = dhdsdio_readframes(bus, rxlimit, &rxdone);
        if (rxdone || bus->rxskip) {
            intstatus &= ~FRAME_AVAIL_MASK(bus);
        }
        rxlimit -= MIN(framecnt, rxlimit);
    }
    /* Keep still-pending events for next scheduling */
    bus->intstatus = intstatus;

clkwait:
    /* Re-enable interrupts to detect new device events (mailbox, rx frame)
     * or clock availability.  (Allows tx loop to check ipend if desired.)
     * (Unless register access seems hosed, as we may not be able to ACK...)
     */
    if (bus->intr && bus->intdis && !bcmsdh_regfail(sdh) &&
        !(bus->dhd->conf->oob_enabled_later && !bus->ctrl_frame_stat)) {
        DHD_INTR(("%s: enable SDIO interrupts, rxdone %d framecnt %d\n",
                  __FUNCTION__, rxdone, framecnt));
        bus->intdis = FALSE;
#if defined(OOB_INTR_ONLY)
        bcmsdh_oob_intr_set(bus->sdh, TRUE);
#endif /* defined(OOB_INTR_ONLY) */
        bcmsdh_intr_enable(sdh);
    }

#if defined(OOB_INTR_ONLY) && !defined(HW_OOB)
    /* In case of SW-OOB(using edge trigger),
     * Check interrupt status in the dongle again after enable irq on the host.
     * and rechedule dpc if interrupt is pended in the dongle.
     * There is a chance to miss OOB interrupt while irq is disabled on the
     * host. No need to do this with HW-OOB(level trigger)
     */
    R_SDREG(newstatus, &regs->intstatus, retries);
    if (bcmsdh_regfail(bus->sdh)) {
        newstatus = 0;
    }
    if (newstatus & bus->hostintmask) {
        bus->ipend = TRUE;
        resched = TRUE;
    }
#endif /* defined(OOB_INTR_ONLY) && !defined(HW_OOB) */

#ifdef BCMSDIO_RXLIM_POST
    if (!DATAOK(bus) && bus->rxlim_en) {
        uint8 rxlim = 0;
        if (0 ==
            dhdsdio_membytes(bus, FALSE, bus->rxlim_addr, (uint8 *)&rxlim, 1)) {
            if (bus->tx_max != rxlim) {
                DHD_INFO(("%s: bus->tx_max/rxlim=%d/%d\n", __FUNCTION__,
                          bus->tx_max, rxlim));
                bus->tx_max = rxlim;
            }
        }
    }
#endif /* BCMSDIO_RXLIM_POST */

#ifdef PROP_TXSTATUS
    dhd_wlfc_commit_packets(bus->dhd, (f_commitpkt_t)dhd_bus_txdata,
                            (void *)bus, NULL, FALSE);
#endif // endif

    if (TXCTLOK(bus) && bus->ctrl_frame_stat && (bus->clkstate == CLK_AVAIL)) {
        dhdsdio_sendpendctl(bus);
    }
#ifdef CONSOLE_DPC
    else if (DATAOK(bus) && strlen(bus->cons_cmd) &&
             (bus->clkstate == CLK_AVAIL) && !bus->fcstate) {
        dhd_bus_console_in(bus->dhd, bus->cons_cmd, strlen(bus->cons_cmd));
    }
#endif

    /* Send queued frames (limit 1 if rx may still be pending) */
    else if ((bus->clkstate == CLK_AVAIL) && !bus->fcstate &&
             pktq_mlen(&bus->txq, ~bus->flowcontrol) && txlimit &&
             DATAOK(bus)) {
#ifdef DHD_ULP
        if (dhd_ulp_f2_ready(bus->dhd, bus->sdh)) {
#endif /* DHD_ULP */
            if (bus->dhd->conf->dhd_txminmax < 0) {
                framecnt = rxdone ? txlimit : MIN(txlimit, DATABUFCNT(bus));
            } else {
                framecnt = rxdone ? txlimit
                                  : MIN(txlimit, bus->dhd->conf->dhd_txminmax);
            }
            framecnt = dhdsdio_sendfromq(bus, framecnt);
            txlimit -= framecnt;
#ifdef DHD_ULP
        } else {
            /* In other transient states like DHD_ULP_, after the states are
             * DHD_ULP_F2ENAB_CLEARING and DHD_ULP_F2ENAB_SETTING,
             * dpc is scheduled after steady-state and dhdsdio_sendfromq() will
             * execute again
             */
        }
#endif /* DHD_ULP */
    }
    /* Resched the DPC if ctrl cmd is pending on bus credit */
    if (bus->ctrl_frame_stat) {
        if (bus->dhd->conf->txctl_tmo_fix) {
            set_current_state(TASK_INTERRUPTIBLE);
            if (!kthread_should_stop()) {
                schedule_timeout(1);
            }
            set_current_state(TASK_RUNNING);
        }
        resched = TRUE;
    }

    /* Resched if events or tx frames are pending, else await next interrupt */
    /* On failed register access, all bets are off: no resched or interrupts */
    if ((bus->dhd->busstate == DHD_BUS_DOWN) || bcmsdh_regfail(sdh)) {
        if ((bus->sih && bus->sih->buscorerev >= 0xC) &&
            !(dhdsdio_sleepcsr_get(bus) & SBSDIO_FUNC1_SLEEPCSR_KSO_MASK)) {
            /* Bus failed because of KSO */
            DHD_ERROR(("%s: Bus failed due to KSO\n", __FUNCTION__));
            bus->kso = FALSE;
        } else {
            DHD_ERROR(
                ("%s: failed backplane access over SDIO, halting operation\n",
                 __FUNCTION__));
            DHD_ERROR(("%s: making DHD_BUS_DOWN\n", __FUNCTION__));
            bus->dhd->busstate = DHD_BUS_DOWN;
            bus->intstatus = 0;
        }
    } else if (bus->clkstate == CLK_PENDING) {
        /* Awaiting I_CHIPACTIVE; don't resched */
    } else if (bus->intstatus || bus->ipend ||
               (!bus->fcstate && pktq_mlen(&bus->txq, ~bus->flowcontrol) &&
                DATAOK(bus)) ||
               PKT_AVAILABLE(bus, bus->intstatus)) { /* Read multiple frames */
        resched = TRUE;
    }

    bus->dpc_sched = resched;

    /* If we're done for now, turn off clock request. */
    if ((bus->idletime == DHD_IDLE_IMMEDIATE) &&
        (bus->clkstate != CLK_PENDING) && NO_OTHER_ACTIVE_BUS_USER(bus)) {
        bus->activity = FALSE;
        dhdsdio_bussleep(bus, TRUE);
        dhdsdio_clkctl(bus, CLK_NONE, FALSE);
    }

exit:

    if (!resched) {
        /* Re-enable interrupts to detect new device events (mailbox, rx frame)
         * or clock availability.  (Allows tx loop to check ipend if desired.)
         * (Unless register access seems hosed, as we may not be able to ACK...)
         */
        if (bus->intr && bus->intdis && !bcmsdh_regfail(sdh) &&
            (bus->dhd->conf->oob_enabled_later && !bus->ctrl_frame_stat)) {
            DHD_INTR(("%s: enable SDIO interrupts, rxdone %d framecnt %d\n",
                      __FUNCTION__, rxdone, framecnt));
            bus->intdis = FALSE;
#if defined(OOB_INTR_ONLY)
            bcmsdh_oob_intr_set(bus->sdh, TRUE);
#endif /* defined(OOB_INTR_ONLY) */
            bcmsdh_intr_enable(sdh);
        }
        if (dhd_dpcpoll) {
            if (dhdsdio_readframes(bus, dhd_rxbound, &rxdone) != 0) {
                resched = TRUE;
#ifdef DEBUG_DPC_THREAD_WATCHDOG
                is_resched_by_readframe = TRUE;
#endif /* DEBUG_DPC_THREAD_WATCHDOG */
            }
        }
    }

#ifdef TPUT_MONITOR
    dhd_conf_tput_monitor(bus->dhd);
#endif

    if (bus->ctrl_wait && TXCTLOK(bus)) {
        wake_up_interruptible(&bus->ctrl_tx_wait);
    }
    dhd_os_sdunlock(bus->dhd);
#ifdef DEBUG_DPC_THREAD_WATCHDOG
    if (bus->dhd->dhd_bug_on) {
        DHD_INFO(("%s: resched = %d ctrl_frame_stat = %d intstatus 0x%08x"
                  " ipend = %d pktq_mlen = %d is_resched_by_readframe = %d \n",
                  __FUNCTION__, resched, bus->ctrl_frame_stat, bus->intstatus,
                  bus->ipend, pktq_mlen(&bus->txq, ~bus->flowcontrol),
                  is_resched_by_readframe));

        bus->dhd->dhd_bug_on = FALSE;
    }
#endif /* DEBUG_DPC_THREAD_WATCHDOG */

    DHD_LINUX_GENERAL_LOCK(bus->dhd, flags);
    DHD_BUS_BUSY_CLEAR_IN_DPC(bus->dhd);
    dhd_os_busbusy_wake(bus->dhd);
    DHD_LINUX_GENERAL_UNLOCK(bus->dhd, flags);

    return resched;
}

bool dhd_bus_dpc(struct dhd_bus *bus)
{
    bool resched;

    /* Call the DPC directly. */
    DHD_TRACE(("Calling dhdsdio_dpc() from %s\n", __FUNCTION__));
    resched = dhdsdio_dpc(bus);

    return resched;
}

void dhdsdio_isr(void *arg)
{
    dhd_bus_t *bus = (dhd_bus_t *)arg;
    bcmsdh_info_t *sdh;

    if (!bus) {
        DHD_ERROR(("%s : bus is null pointer , exit \n", __FUNCTION__));
        return;
    }
    sdh = bus->sdh;

    if (bus->dhd->busstate == DHD_BUS_DOWN) {
        DHD_ERROR(("%s : bus is down. we have nothing to do\n", __FUNCTION__));
        return;
    }

    DHD_INTR(("%s: Enter\n", __FUNCTION__));

    /* Count the interrupt call */
    bus->intrcount++;
    bus->ipend = TRUE;

    /* Shouldn't get this interrupt if we're sleeping? */
    if (!SLPAUTO_ENAB(bus)) {
        if (bus->sleeping) {
            DHD_ERROR(("INTERRUPT WHILE SLEEPING??\n"));
            return;
        } else if (!KSO_ENAB(bus)) {
            DHD_ERROR(("ISR in devsleep 1\n"));
        }
    }

    /* Disable additional interrupts (is this needed now)? */
    if (bus->intr) {
        DHD_INTR(("%s: disable SDIO interrupts\n", __FUNCTION__));
    } else {
        DHD_ERROR(("dhdsdio_isr() w/o interrupt configured!\n"));
    }

    bcmsdh_intr_disable(sdh);
    bus->intdis = TRUE;

#if defined(SDIO_ISR_THREAD)
    DHD_TRACE(("Calling dhdsdio_dpc() from %s\n", __FUNCTION__));
    DHD_OS_WAKE_LOCK(bus->dhd);
    /* terence 20150209: dpc should be scheded again if dpc_sched is TRUE or
       dhd_bus_txdata can not schedule anymore because dpc_sched is TRUE now.
     */
    if (dhdsdio_dpc(bus)) {
        bus->dpc_sched = TRUE;
        dhd_sched_dpc(bus->dhd);
    }
    DHD_OS_WAKE_UNLOCK(bus->dhd);
#else
    bus->dpc_sched = TRUE;
    dhd_sched_dpc(bus->dhd);
#endif /* defined(SDIO_ISR_THREAD) */
}

#ifdef PKT_STATICS
void dhd_bus_dump_txpktstatics(dhd_pub_t *dhdp)
{
    dhd_bus_t *bus = dhdp->bus;
    uint32 total = 0;
    uint i;

    printf("%s: TYPE EVENT: %d pkts (size=%d) transfered\n", __FUNCTION__,
           bus->tx_statics.event_count, bus->tx_statics.event_size);
    printf("%s: TYPE CTRL:  %d pkts (size=%d) transfered\n", __FUNCTION__,
           bus->tx_statics.ctrl_count, bus->tx_statics.ctrl_size);
    printf("%s: TYPE DATA:  %d pkts (size=%d) transfered\n", __FUNCTION__,
           bus->tx_statics.data_count, bus->tx_statics.data_size);
    printf("%s: Glom size distribution:\n", __FUNCTION__);
    for (i = 0; i < bus->tx_statics.glom_max; i++) {
        total += bus->tx_statics.glom_cnt[i];
    }
    printk(KERN_CONT DHD_LOG_PREFIXS);
    for (i = 0; i < bus->tx_statics.glom_max; i++) {
        printk(KERN_CONT "%02d: %5d", i + 1, bus->tx_statics.glom_cnt[i]);
        if ((i + 1) % 0x8) {
            printk(KERN_CONT ", ");
        } else {
            printk("\n");
            printk(KERN_CONT DHD_LOG_PREFIXS);
        }
    }
    printk("\n");
    printk(KERN_CONT DHD_LOG_PREFIXS);
    for (i = 0; i < bus->tx_statics.glom_max; i++) {
        printk(KERN_CONT "%02d:%5d%%", i + 1,
               (bus->tx_statics.glom_cnt[i] * 0x64) / total);
        if ((i + 1) % 0x8) {
            printk(KERN_CONT ", ");
        } else {
            printk("\n");
            printk(KERN_CONT DHD_LOG_PREFIXS);
        }
    }
    printk("\n");
    printf("%s: Glom spend time distribution(us):\n", __FUNCTION__);
    printk(KERN_CONT DHD_LOG_PREFIXS);
    for (i = 0; i < bus->tx_statics.glom_max; i++) {
        printk(KERN_CONT "%02d: %5u", i + 1, bus->tx_statics.glom_cnt_us[i]);
        if ((i + 1) % 0x8) {
            printk(KERN_CONT ", ");
        } else {
            printk("\n");
            printk(KERN_CONT DHD_LOG_PREFIXS);
        }
    }
    printk("\n");
    if (total) {
        printf("%s: data(%d)/glom(%d)=%d, glom_max=%d\n", __FUNCTION__,
               bus->tx_statics.data_count, total,
               bus->tx_statics.data_count / total, bus->tx_statics.glom_max);
    }
    printf("%s: TYPE RX GLOM: %d pkts (size=%d) transfered\n", __FUNCTION__,
           bus->tx_statics.glom_count, bus->tx_statics.glom_size);
    printf("%s: TYPE TEST: %d pkts (size=%d) transfered\n", __FUNCTION__,
           bus->tx_statics.test_count, bus->tx_statics.test_size);

#ifdef KSO_DEBUG
    printf("%s: kso try distribution(us):\n", __FUNCTION__);
    printk(KERN_CONT DHD_LOG_PREFIXS);
    for (i = 0; i < 0xA; i++) {
        printk(KERN_CONT "[%d]: %d, ", i, dhdp->conf->kso_try_array[i]);
    }
    printk("\n");
#endif
}

void dhd_bus_clear_txpktstatics(dhd_pub_t *dhdp)
{
    dhd_bus_t *bus = dhdp->bus;
    memset((uint8 *)&bus->tx_statics, 0, sizeof(pkt_statics_t));
}
#endif

#ifdef SDTEST
static void dhdsdio_pktgen_init(dhd_bus_t *bus)
{
    /* Default to specified length, or full range */
    if (dhd_pktgen_len) {
        bus->pktgen_maxlen = MIN(dhd_pktgen_len, MAX_PKTGEN_LEN);
        bus->pktgen_minlen = bus->pktgen_maxlen;
    } else {
        bus->pktgen_maxlen = MAX_PKTGEN_LEN;
        bus->pktgen_minlen = 0;
    }
    bus->pktgen_len = (uint16)bus->pktgen_minlen;

    /* Default to per-watchdog burst with 10s print time */
    bus->pktgen_freq = 1;
    bus->pktgen_print = dhd_watchdog_ms ? (0x2710 / dhd_watchdog_ms) : 0;
    bus->pktgen_count = (dhd_pktgen * dhd_watchdog_ms + 0x3E7) / 0x3E8;

    /* Default to echo mode */
    bus->pktgen_mode = DHD_PKTGEN_ECHO;
    bus->pktgen_stop = 1;
}

static void dhdsdio_pktgen(dhd_bus_t *bus)
{
    void *pkt;
    uint8 *data;
    uint pktcount;
    uint fillbyte;
    osl_t *osh = bus->dhd->osh;
    uint16 len;
    ulong time_lapse;
    uint sent_pkts;
    uint rcvd_pkts;

    /* Display current count if appropriate */
    if (bus->pktgen_print && (++bus->pktgen_ptick >= bus->pktgen_print)) {
        bus->pktgen_ptick = 0;
        printf("%s: send attempts %d, rcvd %d, errors %d\n", __FUNCTION__,
               bus->pktgen_sent, bus->pktgen_rcvd, bus->pktgen_fail);

        /* Print throughput stats only for constant length packet runs */
        if (bus->pktgen_minlen == bus->pktgen_maxlen) {
            time_lapse = jiffies - bus->pktgen_prev_time;
            bus->pktgen_prev_time = jiffies;
            sent_pkts = bus->pktgen_sent - bus->pktgen_prev_sent;
            bus->pktgen_prev_sent = bus->pktgen_sent;
            rcvd_pkts = bus->pktgen_rcvd - bus->pktgen_prev_rcvd;
            bus->pktgen_prev_rcvd = bus->pktgen_rcvd;

            printf(
                "%s: Tx Throughput %d kbps, Rx Throughput %d kbps\n",
                __FUNCTION__,
                (sent_pkts * bus->pktgen_len / jiffies_to_msecs(time_lapse)) *
                    0x8,
                (rcvd_pkts * bus->pktgen_len / jiffies_to_msecs(time_lapse)) *
                    0x8);
        }
    }

    /* For recv mode, just make sure dongle has started sending */
    if (bus->pktgen_mode == DHD_PKTGEN_RECV) {
        if (bus->pktgen_rcv_state == PKTGEN_RCV_IDLE) {
            bus->pktgen_rcv_state = PKTGEN_RCV_ONGOING;
            dhdsdio_sdtest_set(bus, bus->pktgen_total);
        }
        return;
    }

    /* Otherwise, generate or request the specified number of packets */
    for (pktcount = 0; pktcount < bus->pktgen_count; pktcount++) {
        /* Stop if total has been reached */
        if (bus->pktgen_total && (bus->pktgen_sent >= bus->pktgen_total)) {
            bus->pktgen_count = 0;
            break;
        }

        /* Allocate an appropriate-sized packet */
        if (bus->pktgen_mode == DHD_PKTGEN_RXBURST) {
            len = SDPCM_TEST_PKT_CNT_FLD_LEN;
        } else {
            len = bus->pktgen_len;
        }
        if (!(pkt = PKTGET(
                  osh, (len + SDPCM_HDRLEN + SDPCM_TEST_HDRLEN + DHD_SDALIGN),
                  TRUE))) {
            ;
            DHD_ERROR(("%s: PKTGET failed!\n", __FUNCTION__));
            break;
        }
        PKTALIGN(osh, pkt, (len + SDPCM_HDRLEN + SDPCM_TEST_HDRLEN),
                 DHD_SDALIGN);
        data = (uint8 *)PKTDATA(osh, pkt) + SDPCM_HDRLEN;

        /* Write test header cmd and extra based on mode */
        switch (bus->pktgen_mode) {
            case DHD_PKTGEN_ECHO:
                *data++ = SDPCM_TEST_ECHOREQ;
                *data++ = (uint8)bus->pktgen_sent;
                break;

            case DHD_PKTGEN_SEND:
                *data++ = SDPCM_TEST_DISCARD;
                *data++ = (uint8)bus->pktgen_sent;
                break;

            case DHD_PKTGEN_RXBURST:
                *data++ = SDPCM_TEST_BURST;
                *data++ =
                    (uint8)
                        bus->pktgen_count; /* Just for backward compatability */
                break;

            default:
                DHD_ERROR(("Unrecognized pktgen mode %d\n", bus->pktgen_mode));
                PKTFREE(osh, pkt, TRUE);
                bus->pktgen_count = 0;
                return;
        }

        /* Write test header length field */
        *data++ = (bus->pktgen_len >> 0);
        *data++ = (bus->pktgen_len >> 0x8);

        /* Write frame count in a 4 byte field adjucent to SDPCM test header for
         * burst mode
         */
        if (bus->pktgen_mode == DHD_PKTGEN_RXBURST) {
            *data++ = (uint8)(bus->pktgen_count >> 0);
            *data++ = (uint8)(bus->pktgen_count >> 0x8);
            *data++ = (uint8)(bus->pktgen_count >> 0x10);
            *data++ = (uint8)(bus->pktgen_count >> 0x18);
        } else {
            /* Then fill in the remainder -- N/A for burst */
            for (fillbyte = 0; fillbyte < len; fillbyte++) {
                *data++ = SDPCM_TEST_FILL(fillbyte, (uint8)bus->pktgen_sent);
            }
        }

#ifdef DHD_DEBUG
        if (DHD_BYTES_ON() && DHD_DATA_ON()) {
            data = (uint8 *)PKTDATA(osh, pkt) + SDPCM_HDRLEN;
            prhex("dhdsdio_pktgen: Tx Data", data,
                  PKTLEN(osh, pkt) - SDPCM_HDRLEN);
        }
#endif // endif

        /* Send it */
        if (dhdsdio_txpkt(bus, SDPCM_TEST_CHANNEL, &pkt, 1, TRUE) != BCME_OK) {
            bus->pktgen_fail++;
            if (bus->pktgen_stop && bus->pktgen_stop == bus->pktgen_fail) {
                bus->pktgen_count = 0;
            }
        }
        bus->pktgen_sent++;

        /* Bump length if not fixed, wrap at max */
        if (++bus->pktgen_len > bus->pktgen_maxlen) {
            bus->pktgen_len = (uint16)bus->pktgen_minlen;
        }

        /* Special case for burst mode: just send one request! */
        if (bus->pktgen_mode == DHD_PKTGEN_RXBURST) {
            break;
        }
    }
}

static void dhdsdio_sdtest_set(dhd_bus_t *bus, uint count)
{
    void *pkt;
    uint8 *data;
    osl_t *osh = bus->dhd->osh;

    /* Allocate the packet */
    if (!(pkt = PKTGET(osh,
                       SDPCM_HDRLEN + SDPCM_TEST_HDRLEN +
                           SDPCM_TEST_PKT_CNT_FLD_LEN + DHD_SDALIGN,
                       TRUE))) {
        DHD_ERROR(("%s: PKTGET failed!\n", __FUNCTION__));
        return;
    }
    PKTALIGN(osh, pkt,
             (SDPCM_HDRLEN + SDPCM_TEST_HDRLEN + SDPCM_TEST_PKT_CNT_FLD_LEN),
             DHD_SDALIGN);
    data = (uint8 *)PKTDATA(osh, pkt) + SDPCM_HDRLEN;

    /* Fill in the test header */
    *data++ = SDPCM_TEST_SEND;
    *data++ = (count > 0) ? TRUE : FALSE;
    *data++ = (bus->pktgen_maxlen >> 0);
    *data++ = (bus->pktgen_maxlen >> 0x8);
    *data++ = (uint8)(count >> 0);
    *data++ = (uint8)(count >> 0x8);
    *data++ = (uint8)(count >> 0x10);
    *data++ = (uint8)(count >> 0x18);

    /* Send it */
    if (dhdsdio_txpkt(bus, SDPCM_TEST_CHANNEL, &pkt, 1, TRUE) != BCME_OK) {
        bus->pktgen_fail++;
    }
}

static void dhdsdio_testrcv(dhd_bus_t *bus, void *pkt, uint seq)
{
    osl_t *osh = bus->dhd->osh;
    uint8 *data;
    uint pktlen;

    uint8 cmd;
    uint8 extra;
    uint16 len;
    uint16 offset;

    /* Check for min length */
    if ((pktlen = PKTLEN(osh, pkt)) < SDPCM_TEST_HDRLEN) {
        DHD_ERROR(("dhdsdio_restrcv: toss runt frame, pktlen %d\n", pktlen));
        PKTFREE(osh, pkt, FALSE);
        return;
    }

    /* Extract header fields */
    data = PKTDATA(osh, pkt);
    cmd = *data++;
    extra = *data++;
    len = *data++;
    len += *data++ << 0x8;
    DHD_TRACE(("%s:cmd:%d, xtra:%d,len:%d\n", __FUNCTION__, cmd, extra, len));
    /* Check length for relevant commands */
    if (cmd == SDPCM_TEST_DISCARD || cmd == SDPCM_TEST_ECHOREQ ||
        cmd == SDPCM_TEST_ECHORSP) {
        if (pktlen != len + SDPCM_TEST_HDRLEN) {
            DHD_ERROR(
                ("dhdsdio_testrcv: frame length mismatch, pktlen %d seq %d"
                 " cmd %d extra %d len %d\n",
                 pktlen, seq, cmd, extra, len));
            PKTFREE(osh, pkt, FALSE);
            return;
        }
    }

    /* Process as per command */
    switch (cmd) {
        case SDPCM_TEST_ECHOREQ:
            /* Rx->Tx turnaround ok (even on NDIS w/current implementation) */
            *(uint8 *)(PKTDATA(osh, pkt)) = SDPCM_TEST_ECHORSP;
            if (dhdsdio_txpkt(bus, SDPCM_TEST_CHANNEL, &pkt, 1, TRUE) ==
                BCME_OK) {
                bus->pktgen_sent++;
            } else {
                bus->pktgen_fail++;
                PKTFREE(osh, pkt, FALSE);
            }
            bus->pktgen_rcvd++;
            break;

        case SDPCM_TEST_ECHORSP:
            if (bus->ext_loop) {
                PKTFREE(osh, pkt, FALSE);
                bus->pktgen_rcvd++;
                break;
            }

            for (offset = 0; offset < len; offset++, data++) {
                if (*data != SDPCM_TEST_FILL(offset, extra)) {
                    DHD_ERROR(("dhdsdio_testrcv: echo data mismatch: "
                               "offset %d (len %d) expect 0x%02x rcvd 0x%02x\n",
                               offset, len, SDPCM_TEST_FILL(offset, extra),
                               *data));
                    break;
                }
            }
            PKTFREE(osh, pkt, FALSE);
            bus->pktgen_rcvd++;
            break;

        case SDPCM_TEST_DISCARD: {
            int i = 0;
            uint8 *prn = data;
            uint8 testval = extra;
            for (i = 0; i < len; i++) {
                if (*prn != testval) {
                    DHD_ERROR(("DIErr@Pkt#:%d,Ix:%d, expected:0x%x, got:0x%x\n",
                               i, bus->pktgen_rcvd_rcvsession, testval, *prn));
                    prn++;
                    testval++;
                }
            }
        }
            PKTFREE(osh, pkt, FALSE);
            bus->pktgen_rcvd++;
            break;

        case SDPCM_TEST_BURST:
        case SDPCM_TEST_SEND:
        default:
            DHD_INFO(("dhdsdio_testrcv: unsupported or unknown command, pktlen "
                      "%d seq %d"
                      " cmd %d extra %d len %d\n",
                      pktlen, seq, cmd, extra, len));
            PKTFREE(osh, pkt, FALSE);
            break;
    }

    /* For recv mode, stop at limit (and tell dongle to stop sending) */
    if (bus->pktgen_mode == DHD_PKTGEN_RECV) {
        if (bus->pktgen_rcv_state != PKTGEN_RCV_IDLE) {
            bus->pktgen_rcvd_rcvsession++;

            if (bus->pktgen_total &&
                (bus->pktgen_rcvd_rcvsession >= bus->pktgen_total)) {
                bus->pktgen_count = 0;
                DHD_ERROR(("Pktgen:rcv test complete!\n"));
                bus->pktgen_rcv_state = PKTGEN_RCV_IDLE;
                dhdsdio_sdtest_set(bus, FALSE);
                bus->pktgen_rcvd_rcvsession = 0;
            }
        }
    }
}
#endif /* SDTEST */

int dhd_bus_oob_intr_register(dhd_pub_t *dhdp)
{
    int err = 0;

#if defined(OOB_INTR_ONLY)
    err = bcmsdh_oob_intr_register(dhdp->bus->sdh, dhdsdio_isr, dhdp->bus);
#endif // endif
    return err;
}

void dhd_bus_oob_intr_unregister(dhd_pub_t *dhdp)
{
#if defined(OOB_INTR_ONLY)
    bcmsdh_oob_intr_unregister(dhdp->bus->sdh);
#endif // endif
}

void dhd_bus_oob_intr_set(dhd_pub_t *dhdp, bool enable)
{
#if defined(OOB_INTR_ONLY)
    bcmsdh_oob_intr_set(dhdp->bus->sdh, enable);
#endif // endif
}

void dhd_bus_dev_pm_stay_awake(dhd_pub_t *dhdpub)
{
    bcmsdh_dev_pm_stay_awake(dhdpub->bus->sdh);
}

void dhd_bus_dev_pm_relax(dhd_pub_t *dhdpub)
{
    bcmsdh_dev_relax(dhdpub->bus->sdh);
}

bool dhd_bus_dev_pm_enabled(dhd_pub_t *dhdpub)
{
    bool enabled = FALSE;

    enabled = bcmsdh_dev_pm_enabled(dhdpub->bus->sdh);
    return enabled;
}

extern bool dhd_bus_watchdog(dhd_pub_t *dhdp)
{
    dhd_bus_t *bus;
    unsigned long flags;

    DHD_TIMER(("%s: Enter\n", __FUNCTION__));

    bus = dhdp->bus;

    if (bus->dhd->dongle_reset) {
        return FALSE;
    }

    if (bus->dhd->hang_was_sent) {
        dhd_os_wd_timer(bus->dhd, 0);
        return FALSE;
    }

    /* Ignore the timer if simulating bus down */
    if (!SLPAUTO_ENAB(bus) && bus->sleeping) {
        return FALSE;
    }

    DHD_LINUX_GENERAL_LOCK(dhdp, flags);
    if (DHD_BUS_CHECK_DOWN_OR_DOWN_IN_PROGRESS(dhdp) ||
        DHD_BUS_CHECK_SUSPEND_OR_SUSPEND_IN_PROGRESS(dhdp)) {
        DHD_LINUX_GENERAL_UNLOCK(dhdp, flags);
        return FALSE;
    }
    DHD_BUS_BUSY_SET_IN_WD(dhdp);
    DHD_LINUX_GENERAL_UNLOCK(dhdp, flags);

    dhd_os_sdlock(bus->dhd);

    /* Poll period: check device if appropriate. */
    // terence 20160615: remove !SLPAUTO_ENAB(bus) to fix not able to polling if
    // sr supported
    if (1 && (bus->poll && (++bus->polltick >= bus->pollrate))) {
        uint32 intstatus = 0;

        /* Reset poll tick */
        bus->polltick = 0;

        /* Check device if no interrupts */
        if (!bus->intr || (bus->intrcount == bus->lastintrs)) {
#ifndef BCMSPI
            if (!bus->dpc_sched) {
                uint8 devpend;
                devpend = bcmsdh_cfg_read(bus->sdh, SDIO_FUNC_0,
                                          SDIOD_CCCR_INTPEND, NULL);
                intstatus = devpend & (INTR_STATUS_FUNC1 | INTR_STATUS_FUNC2);
            }
#else
            if (!bus->dpc_sched) {
                uint32 devpend;
                devpend = bcmsdh_cfg_read_word(bus->sdh, SDIO_FUNC_0,
                                               SPID_STATUS_REG, NULL);
                intstatus = devpend & STATUS_F2_PKT_AVAILABLE;
            }
#endif /* !BCMSPI */

            /* If there is something, make like the ISR and schedule the DPC */
            if (intstatus) {
                bus->pollcnt++;
                bus->ipend = TRUE;
                if (bus->intr) {
                    bcmsdh_intr_disable(bus->sdh);
                }
                bus->dpc_sched = TRUE;
                dhd_sched_dpc(bus->dhd);
            }
        }

        /* Update interrupt tracking */
        bus->lastintrs = bus->intrcount;
    }

    if ((!bus->dpc_sched) && pktq_n_pkts_tot(&bus->txq)) {
        bus->dpc_sched = TRUE;
        dhd_sched_dpc(bus->dhd);
    }

#ifdef DHD_DEBUG
    /* Poll for console output periodically */
    if (dhdp->busstate == DHD_BUS_DATA && dhdp->dhd_console_ms != 0) {
        bus->console.count += dhd_watchdog_ms;
        if (bus->console.count >= dhdp->dhd_console_ms) {
            bus->console.count -= dhdp->dhd_console_ms;
            /* Make sure backplane clock is on */
            if (SLPAUTO_ENAB(bus)) {
                dhdsdio_bussleep(bus, FALSE);
            } else {
                dhdsdio_clkctl(bus, CLK_AVAIL, FALSE);
            }
            if (dhdsdio_readconsole(bus) < 0) {
                dhdp->dhd_console_ms = 0; /* On error, stop trying */
            }
        }
    }
#endif /* DHD_DEBUG */

#ifdef SDTEST
    /* Generate packets if configured */
    if (bus->pktgen_count && (++bus->pktgen_tick >= bus->pktgen_freq)) {
        /* Make sure backplane clock is on */
        if (SLPAUTO_ENAB(bus)) {
            dhdsdio_bussleep(bus, FALSE);
        } else {
            dhdsdio_clkctl(bus, CLK_AVAIL, FALSE);
        }
        bus->pktgen_tick = 0;
        dhdsdio_pktgen(bus);
    }
#endif // endif

    /* On idle timeout clear activity flag and/or turn off clock */
#ifdef DHD_USE_IDLECOUNT
    if (bus->activity) {
        bus->activity = FALSE;
    } else {
        bus->idlecount++;

        /*
         * If the condition to switch off the clock is reached And if
         * BT is inactive (in case of BT_OVER_SDIO build) turn off clk.
         *
         * Consider the following case, DHD is configured with
         * 1) idletime == DHD_IDLE_IMMEDIATE
         * 2) BT is the last user of the clock
         * We cannot disable the clock from __dhdsdio_clk_disable
         * since WLAN might be using it. If WLAN is active then
         * from the respective function/context after doing the job
         * the clk is turned off.
         * But if WLAN is actually inactive then the watchdog should
         * disable the clock. So the condition check below should be
         * bus->idletime != 0 instead of idletime == 0
         */
        if ((bus->idletime != 0) && (bus->idlecount >= bus->idletime) &&
            NO_OTHER_ACTIVE_BUS_USER(bus)) {
            DHD_TIMER(("%s: DHD Idle state!!\n", __FUNCTION__));
            if (!bus->poll && SLPAUTO_ENAB(bus)) {
                if (dhdsdio_bussleep(bus, TRUE) != BCME_BUSY) {
                    dhd_os_wd_timer(bus->dhd, 0);
                }
            } else {
                dhdsdio_clkctl(bus, CLK_NONE, FALSE);
            }

            bus->idlecount = 0;
        }
    }
#else
    if ((bus->idletime != 0) && (bus->clkstate == CLK_AVAIL) &&
        NO_OTHER_ACTIVE_BUS_USER(bus)) {
        if (++bus->idlecount >= bus->idletime) {
            bus->idlecount = 0;
            if (bus->activity) {
                bus->activity = FALSE;
                if (!bus->poll && SLPAUTO_ENAB(bus)) {
                    if (!bus->readframes) {
                        dhdsdio_bussleep(bus, TRUE);
                    } else {
                        bus->reqbussleep = TRUE;
                    }
                } else {
                    dhdsdio_clkctl(bus, CLK_NONE, FALSE);
                }
            }
        }
    }
#endif /* DHD_USE_IDLECOUNT */

    dhd_os_sdunlock(bus->dhd);

    DHD_LINUX_GENERAL_LOCK(dhdp, flags);
    DHD_BUS_BUSY_CLEAR_IN_WD(dhdp);
    dhd_os_busbusy_wake(dhdp);
    DHD_LINUX_GENERAL_UNLOCK(dhdp, flags);

    return bus->ipend;
}

extern int dhd_bus_console_in(dhd_pub_t *dhdp, uchar *msg, uint msglen)
{
    dhd_bus_t *bus = dhdp->bus;
    uint32 addr, val;
    int rv;
    void *pkt;

#ifndef CONSOLE_DPC
    /* Exclusive bus access */
    dhd_os_sdlock(bus->dhd);
#endif

    /* Address could be zero if CONSOLE := 0 in dongle Makefile */
    if (bus->console_addr == 0) {
        rv = BCME_UNSUPPORTED;
        goto exit;
    }

    /* Don't allow input if dongle is in reset */
    if (bus->dhd->dongle_reset) {
        rv = BCME_NOTREADY;
        goto exit;
    }

#ifndef CONSOLE_DPC
    if (!DATAOK(bus)) {
        DHD_CTL(
            ("%s: No bus credit bus->tx_max %d, bus->tx_seq %d, pktq_len %d\n",
             __FUNCTION__, bus->tx_max, bus->tx_seq,
             pktq_n_pkts_tot(&bus->txq)));
        rv = BCME_NOTREADY;
        goto exit;
    }

    /* Request clock to allow SDIO accesses */
    BUS_WAKE(bus);
    /* No pend allowed since txpkt is called later, ht clk has to be on */
    dhdsdio_clkctl(bus, CLK_AVAIL, FALSE);
#endif

    /* Zero cbuf_index */
    addr = bus->console_addr + OFFSETOF(hnd_cons_t, cbuf_idx);
    val = htol32(0);
    if ((rv = dhdsdio_membytes(bus, TRUE, addr, (uint8 *)&val, sizeof(val))) <
        0) {
        goto done;
    }

    /* Write message into cbuf */
    addr = bus->console_addr + OFFSETOF(hnd_cons_t, cbuf);
    if ((rv = dhdsdio_membytes(bus, TRUE, addr, (uint8 *)msg, msglen)) < 0) {
        goto done;
    }

    /* Write length into vcons_in */
    addr = bus->console_addr + OFFSETOF(hnd_cons_t, vcons_in);
    val = htol32(msglen);
    if ((rv = dhdsdio_membytes(bus, TRUE, addr, (uint8 *)&val, sizeof(val))) <
        0) {
        goto done;
    }

    /* Bump dongle by sending an empty packet on the event channel.
     * sdpcm_sendup (RX) checks for virtual console input.
     */
    if ((pkt = PKTGET(bus->dhd->osh, 0x4 + SDPCM_RESERVE, TRUE)) != NULL) {
        rv = dhdsdio_txpkt(bus, SDPCM_EVENT_CHANNEL, &pkt, 1, TRUE);
    }

done:
#ifndef CONSOLE_DPC
    if ((bus->idletime == DHD_IDLE_IMMEDIATE) && !bus->dpc_sched &&
        NO_OTHER_ACTIVE_BUS_USER(bus)) {
        bus->activity = FALSE;
        dhdsdio_bussleep(bus, TRUE);
        dhdsdio_clkctl(bus, CLK_NONE, FALSE);
    }
#endif

exit:
#ifdef CONSOLE_DPC
    memset(bus->cons_cmd, 0, sizeof(bus->cons_cmd));
#else
    dhd_os_sdunlock(bus->dhd);
#endif
    return rv;
}

#ifdef CONSOLE_DPC
extern int dhd_bus_txcons(dhd_pub_t *dhdp, uchar *msg, uint msglen)
{
    dhd_bus_t *bus = dhdp->bus;
    int ret = BCME_OK;

    dhd_os_sdlock(bus->dhd);

    /* Address could be zero if CONSOLE := 0 in dongle Makefile */
    if (bus->console_addr == 0) {
        ret = BCME_UNSUPPORTED;
        goto exit;
    }

    /* Don't allow input if dongle is in reset */
    if (bus->dhd->dongle_reset) {
        ret = BCME_NOTREADY;
        goto exit;
    }

    if (msglen >= sizeof(bus->cons_cmd)) {
        DHD_ERROR(("%s: \"%s\"(%d) too long\n", __FUNCTION__, msg, msglen));
        ret = BCME_BADARG;
        goto exit;
    }

    if (!strlen(bus->cons_cmd)) {
        strncpy(bus->cons_cmd, msg, sizeof(bus->cons_cmd));
        DHD_CTL(("%s: \"%s\" delay send, tx_max %d, tx_seq %d, pktq_len %d\n",
                 __FUNCTION__, bus->cons_cmd, bus->tx_max, bus->tx_seq,
                 pktq_n_pkts_tot(&bus->txq)));
        if (!bus->dpc_sched) {
            bus->dpc_sched = TRUE;
            dhd_sched_dpc(bus->dhd);
        }
    } else {
        DHD_CTL(("%s: \"%s\" is pending, tx_max %d, tx_seq %d, pktq_len %d\n",
                 __FUNCTION__, bus->cons_cmd, bus->tx_max, bus->tx_seq,
                 pktq_n_pkts_tot(&bus->txq)));
        ret = BCME_NOTREADY;
    }

exit:
    dhd_os_sdunlock(bus->dhd);

    return ret;
}
#endif

#if defined(DHD_DEBUG) && !defined(BCMSDIOLITE)
static void dhd_dump_cis(uint fn, uint8 *cis)
{
    uint byte, tag, tdata;
    DHD_INFO(("Function %d CIS:\n", fn));

    for (tdata = byte = 0; byte < SBSDIO_CIS_SIZE_LIMIT; byte++) {
        if ((byte % 0x10) == 0) {
            DHD_INFO(("    "));
        }
        DHD_INFO(("%02x ", cis[byte]));
        if ((byte % 0x10) == 0xF) {
            DHD_INFO(("\n"));
        }
        if (!tdata--) {
            tag = cis[byte];
            if (tag == 0xff) {
                break;
            } else if (!tag) {
                tdata = 0;
            } else if ((byte + 1) < SBSDIO_CIS_SIZE_LIMIT) {
                tdata = cis[byte + 1] + 1;
            } else {
                DHD_INFO(("]"));
            }
        }
    }
    if ((byte % 0x10) != 0xF) {
        DHD_INFO(("\n"));
    }
}
#endif /* DHD_DEBUG */

static bool dhdsdio_chipmatch(uint16 chipid)
{
    if (chipid == BCM4330_CHIP_ID) {
        return TRUE;
    }
    if (chipid == BCM43362_CHIP_ID) {
        return TRUE;
    }
    if (chipid == BCM43340_CHIP_ID) {
        return TRUE;
    }
    if (chipid == BCM43341_CHIP_ID) {
        return TRUE;
    }
    if (chipid == BCM4334_CHIP_ID) {
        return TRUE;
    }
    if (chipid == BCM4324_CHIP_ID) {
        return TRUE;
    }
    if (chipid == BCM4335_CHIP_ID) {
        return TRUE;
    }
    if (chipid == BCM4339_CHIP_ID) {
        return TRUE;
    }
    if (BCM4345_CHIP(chipid)) {
        return TRUE;
    }
    if (chipid == BCM4350_CHIP_ID) {
        return TRUE;
    }
    if (chipid == BCM4354_CHIP_ID) {
        return TRUE;
    }
    if (chipid == BCM4358_CHIP_ID) {
        return TRUE;
    }
    if (chipid == BCM43569_CHIP_ID) {
        return TRUE;
    }
    if (chipid == BCM4371_CHIP_ID) {
        return TRUE;
    }
    if (chipid == BCM43430_CHIP_ID) {
        return TRUE;
    }
    if (chipid == BCM43018_CHIP_ID) {
        return TRUE;
    }
    if (BCM4349_CHIP(chipid)) {
        return TRUE;
    }
    if (chipid == BCM4364_CHIP_ID) {
        return TRUE;
    }

    if (chipid == BCM43012_CHIP_ID) {
        return TRUE;
    }

    if (chipid == BCM43014_CHIP_ID) {
        return TRUE;
    }

    if (chipid == BCM4369_CHIP_ID) {
        return TRUE;
    }
    if (chipid == BCM4362_CHIP_ID) {
        return TRUE;
    }
    if (chipid == BCM43751_CHIP_ID) {
        return TRUE;
    }
    if (chipid == BCM43752_CHIP_ID) {
        return TRUE;
    }

    return FALSE;
}

static void *dhdsdio_probe(uint16 venid, uint16 devid, uint16 bus_no,
                           uint16 slot, uint16 func, uint bustype, void *regsva,
                           osl_t *osh, void *sdh)
{
    int ret;
    dhd_bus_t *bus;

    DHD_MUTEX_LOCK();

    /* Init global variables at run-time, not as part of the declaration.
     * This is required to support init/de-init of the driver. Initialization
     * of globals as part of the declaration results in non-deterministic
     * behavior since the value of the globals may be different on the
     * first time that the driver is initialized vs subsequent initializations.
     */
    dhd_txbound = DHD_TXBOUND;
    dhd_rxbound = DHD_RXBOUND;
#ifdef BCMSPI
    dhd_alignctl = FALSE;
#else
    dhd_alignctl = TRUE;
#endif /* BCMSPI */
    sd1idle = TRUE;
    dhd_readahead = TRUE;
    retrydata = FALSE;

#ifdef DISABLE_FLOW_CONTROL
    dhd_doflow = FALSE;
#endif /* DISABLE_FLOW_CONTROL */
    dhd_dongle_ramsize = 0;
    dhd_txminmax = DHD_TXMINMAX;

#ifdef BCMSPI
    forcealign = FALSE;
#else
    forcealign = TRUE;
#endif /* !BCMSPI */

    DHD_TRACE(("%s: Enter\n", __FUNCTION__));
    DHD_INFO(("%s: venid 0x%04x devid 0x%04x\n", __FUNCTION__, venid, devid));

    /* We make assumptions about address window mappings */
    ASSERT((uintptr)regsva == si_enum_base(devid));

    /* BCMSDH passes venid and devid based on CIS parsing -- but low-power start
     * means early parse could fail, so here we should get either an ID
     * we recognize OR (-1) indicating we must request power first.
     */
    /* Check the Vendor ID */
    switch (venid) {
        case 0x0000:
        case VENDOR_BROADCOM:
            break;
        default:
            DHD_ERROR(("%s: unknown vendor: 0x%04x\n", __FUNCTION__, venid));
            goto forcereturn;
    }

    /* Check the Device ID and make sure it's one that we support */
    switch (devid) {
        case 0:
            DHD_INFO(("%s: allow device id 0, will check chip internals\n",
                      __FUNCTION__));
            break;

        default:
            DHD_ERROR(("%s: skipping 0x%04x/0x%04x, not a dongle\n",
                       __FUNCTION__, venid, devid));
            goto forcereturn;
    }

    if (osh == NULL) {
        DHD_ERROR(("%s: osh is NULL!\n", __FUNCTION__));
        goto forcereturn;
    }

    /* Allocate private bus interface state */
    if (!(bus = MALLOC(osh, sizeof(dhd_bus_t)))) {
        DHD_ERROR(("%s: MALLOC of dhd_bus_t failed\n", __FUNCTION__));
        goto fail;
    }
    bzero(bus, sizeof(dhd_bus_t));
    bus->sdh = sdh;
    bus->cl_devid = (uint16)devid;
    bus->bus = DHD_BUS;
    bus->bus_num = bus_no;
    bus->slot_num = slot;
    bus->tx_seq = SDPCM_SEQUENCE_WRAP - 1;
    bus->usebufpool =
        FALSE; /* Use bufpool if allocated, else use locally malloced rxbuf */
#ifdef BT_OVER_SDIO
    bus->bt_use_count = 0;
#endif // endif

#if defined(SUPPORT_P2P_GO_PS)
    init_waitqueue_head(&bus->bus_sleep);
#endif /* LINUX && SUPPORT_P2P_GO_PS */
    init_waitqueue_head(&bus->ctrl_tx_wait);

    /* attempt to attach to the dongle */
    if (!(dhdsdio_probe_attach(bus, osh, sdh, regsva, devid))) {
        DHD_ERROR(("%s: dhdsdio_probe_attach failed\n", __FUNCTION__));
        goto fail;
    }

    /* Attach to the dhd/OS/network interface */
    if (!(bus->dhd = dhd_attach(osh, bus, SDPCM_RESERVE))) {
        DHD_ERROR(("%s: dhd_attach failed\n", __FUNCTION__));
        goto fail;
    }

#if defined(GET_OTP_MAC_ENABLE) || defined(GET_OTP_MODULE_NAME)
    dhd_conf_get_otp(bus->dhd, sdh, bus->sih);
#endif

    /* Allocate buffers */
    if (!(dhdsdio_probe_malloc(bus, osh, sdh))) {
        DHD_ERROR(("%s: dhdsdio_probe_malloc failed\n", __FUNCTION__));
        goto fail;
    }

    if (!(dhdsdio_probe_init(bus, osh, sdh))) {
        DHD_ERROR(("%s: dhdsdio_probe_init failed\n", __FUNCTION__));
        goto fail;
    }

    if (bus->intr) {
        /* Register interrupt callback, but mask it (not operational yet). */
        DHD_INTR(("%s: disable SDIO interrupts (not interested yet)\n",
                  __FUNCTION__));
        bcmsdh_intr_disable(sdh);
        if ((ret = bcmsdh_intr_reg(sdh, dhdsdio_isr, bus)) != 0) {
            DHD_ERROR(("%s: FAILED: bcmsdh_intr_reg returned %d\n",
                       __FUNCTION__, ret));
            goto fail;
        }
        DHD_INTR(("%s: registered SDIO interrupt function ok\n", __FUNCTION__));
    } else {
        DHD_INFO(("%s: SDIO interrupt function is NOT registered due to "
                  "polling mode\n",
                  __FUNCTION__));
    }

    DHD_INFO(("%s: completed!!\n", __FUNCTION__));

    /* if firmware path present try to download and bring up bus */
    bus->dhd->hang_report = TRUE;
#if defined(BT_OVER_SDIO)
    /* At this point Regulators are turned on and iconditionaly sdio bus is
     * started based upon dhd_download_fw_on_driverload check, so increase the
     * bus user count, this count will only be disabled inside dhd_register_if()
     * function if flag dhd_download_fw_on_driverload is set to false, i.e FW
     * download during insmod is not needed, otherwise it will not be
     * decremented so that WALN will always hold the bus untill rmmod is done.
     */
    dhdsdio_bus_usr_cnt_inc(bus->dhd);
#endif /* BT_OVER_SDIO */

    /* Ok, have the per-port tell the stack we're open for business */
    if (dhd_attach_net(bus->dhd, TRUE) != 0) {
        DHD_ERROR(("%s: Net attach failed!!\n", __FUNCTION__));
        goto fail;
    }

#ifdef BCMHOST_XTAL_PU_TIME_MOD
    bcmsdh_reg_write(bus->sdh, 0x18000620, 0x2, 0xB);
    bcmsdh_reg_write(bus->sdh, 0x18000628, 0x4, 0x00F80001);
#endif /* BCMHOST_XTAL_PU_TIME_MOD */

#if defined(MULTIPLE_SUPPLICANT)
    wl_ohos_post_init(); // terence 20120530: fix critical section in
                            // dhd_open and dhdsdio_probe
#endif                      /* MULTIPLE_SUPPLICANT */
    DHD_MUTEX_UNLOCK();

    return bus;

fail:
    dhdsdio_release(bus, osh);

forcereturn:
    DHD_MUTEX_UNLOCK();

    return NULL;
}

static bool dhdsdio_probe_attach(struct dhd_bus *bus, osl_t *osh, void *sdh,
                                 void *regsva, uint16 devid)
{
#ifndef BCMSPI
    uint8 clkctl = 0;
#endif /* !BCMSPI */
    uint fn, numfn;
    uint8 *cis[SDIOD_MAX_IOFUNCS];
    int32 value;
    int err = 0;

    BCM_REFERENCE(value);
    bus->alp_only = TRUE;
    bus->sih = NULL;

    /* Return the window to backplane enumeration space for core access */
    if (dhdsdio_set_siaddr_window(bus, si_enum_base(devid))) {
        DHD_ERROR(("%s: FAILED to return to SI_ENUM_BASE\n", __FUNCTION__));
    }

#if defined(DHD_DEBUG)
    DHD_ERROR(("F1 signature read @0x18000000=0x%4x\n",
               bcmsdh_reg_read(bus->sdh, si_enum_base(devid), 0x4)));
#endif // endif

#ifndef BCMSPI /* wake-wlan in gSPI will bring up the htavail/alpavail clocks. \
                */

    /* Force PLL off until si_attach() programs PLL control regs */

    bcmsdh_cfg_write(sdh, SDIO_FUNC_1, SBSDIO_FUNC1_CHIPCLKCSR,
                     DHD_INIT_CLKCTL1, &err);
    if (!err) {
        clkctl =
            bcmsdh_cfg_read(sdh, SDIO_FUNC_1, SBSDIO_FUNC1_CHIPCLKCSR, &err);
    }

    if (err || ((clkctl & ~SBSDIO_AVBITS) != DHD_INIT_CLKCTL1)) {
        DHD_ERROR(("dhdsdio_probe: ChipClkCSR access: err %d wrote 0x%02x read "
                   "0x%02x\n",
                   err, DHD_INIT_CLKCTL1, clkctl));
        goto fail;
    }

#endif /* !BCMSPI */
#ifndef BCMSPI
    numfn = bcmsdh_query_iofnum(sdh);
    ASSERT(numfn <= SDIOD_MAX_IOFUNCS);

    /* Make sure ALP is available before trying to read CIS */
    SPINWAIT(((clkctl = bcmsdh_cfg_read(sdh, SDIO_FUNC_1,
                                        SBSDIO_FUNC1_CHIPCLKCSR, NULL)),
              !SBSDIO_ALPAV(clkctl)),
             PMU_MAX_TRANSITION_DLY);

    /* Now request ALP be put on the bus */
    bcmsdh_cfg_write(sdh, SDIO_FUNC_1, SBSDIO_FUNC1_CHIPCLKCSR,
                     DHD_INIT_CLKCTL2, &err);
    OSL_DELAY(0x41);
#else
    numfn =
        0; /* internally func is hardcoded to 1 as gSPI has cis on F1 only */
#endif /* !BCMSPI */
#ifndef BCMSDIOLITE
    if (DHD_INFO_ON()) {
        for (fn = 0; fn <= numfn; fn++) {
            if (!(cis[fn] = MALLOC(osh, SBSDIO_CIS_SIZE_LIMIT))) {
                DHD_INFO(("dhdsdio_probe: fn %d cis malloc failed\n", fn));
                break;
            }
            bzero(cis[fn], SBSDIO_CIS_SIZE_LIMIT);

            if ((err = bcmsdh_cis_read(sdh, fn, cis[fn],
                                       SBSDIO_CIS_SIZE_LIMIT))) {
                DHD_INFO(("dhdsdio_probe: fn %d cis read err %d\n", fn, err));
                MFREE(osh, cis[fn], SBSDIO_CIS_SIZE_LIMIT);
                break;
            }
#ifdef DHD_DEBUG
            if (DHD_INFO_ON()) {
                dhd_dump_cis(fn, cis[fn]);
            }
#endif /* DHD_DEBUG */
        }
        while (fn-- > 0) {
            ASSERT(cis[fn]);
            MFREE(osh, cis[fn], SBSDIO_CIS_SIZE_LIMIT);
        }
    }
#else
    BCM_REFERENCE(cis);
    BCM_REFERENCE(fn);
#endif /* DHD_DEBUG */

    if (err) {
        DHD_ERROR(("dhdsdio_probe: failure reading or parsing CIS\n"));
        goto fail;
    }
    /* si_attach() will provide an SI handle and scan the backplane */
    if (!(bus->sih = si_attach((uint)devid, osh, regsva, DHD_BUS, sdh,
                               &bus->vars, &bus->varsz))) {
        DHD_ERROR(("%s: si_attach failed!\n", __FUNCTION__));
        goto fail;
    }

#ifdef DHD_DEBUG
    DHD_ERROR(("F1 signature OK, socitype:0x%x chip:0x%4x rev:0x%x pkg:0x%x\n",
               bus->sih->socitype, bus->sih->chip, bus->sih->chiprev,
               bus->sih->chippkg));
#endif /* DHD_DEBUG */

    bcmsdh_chipinfo(sdh, bus->sih->chip, bus->sih->chiprev);

    if (!dhdsdio_chipmatch((uint16)bus->sih->chip)) {
        DHD_ERROR(
            ("%s: unsupported chip: 0x%04x\n", __FUNCTION__, bus->sih->chip));
        goto fail;
    }

    if (bus->sih->buscorerev >= 0xC) {
        dhdsdio_clk_kso_init(bus);
    } else {
        bus->kso = TRUE;
    }

    si_sdiod_drive_strength_init(bus->sih, osh, dhd_sdiod_drive_strength);

    /* Get info on the ARM and SOCRAM cores... */
    if (!DHD_NOPMU(bus)) {
        if ((si_setcore(bus->sih, ARM7S_CORE_ID, 0)) ||
            (si_setcore(bus->sih, ARMCM3_CORE_ID, 0)) ||
            (si_setcore(bus->sih, ARMCR4_CORE_ID, 0))) {
            bus->armrev = si_corerev(bus->sih);
        } else {
            DHD_ERROR(("%s: failed to find ARM core!\n", __FUNCTION__));
            goto fail;
        }

        if (!si_setcore(bus->sih, ARMCR4_CORE_ID, 0)) {
            if (!(bus->orig_ramsize = si_socram_size(bus->sih))) {
                DHD_ERROR(
                    ("%s: failed to find SOCRAM memory!\n", __FUNCTION__));
                goto fail;
            }
        } else {
            /* cr4 has a different way to find the RAM size from TCM's */
            if (!(bus->orig_ramsize = si_tcm_size(bus->sih))) {
                DHD_ERROR(
                    ("%s: failed to find CR4-TCM memory!\n", __FUNCTION__));
                goto fail;
            }
            /* also populate base address */
            switch ((uint16)bus->sih->chip) {
                case BCM4335_CHIP_ID:
                case BCM4339_CHIP_ID:
                    bus->dongle_ram_base = CR4_4335_RAM_BASE;
                    break;
                case BCM4350_CHIP_ID:
                case BCM4354_CHIP_ID:
                case BCM4358_CHIP_ID:
                case BCM43569_CHIP_ID:
                case BCM4371_CHIP_ID:
                    bus->dongle_ram_base = CR4_4350_RAM_BASE;
                    break;
                case BCM4360_CHIP_ID:
                    bus->dongle_ram_base = CR4_4360_RAM_BASE;
                    break;
                CASE_BCM4345_CHIP:
                    bus->dongle_ram_base =
                        (bus->sih->chiprev < 0x6) /* from 4345C0 */
                            ? CR4_4345_LT_C0_RAM_BASE
                            : CR4_4345_GE_C0_RAM_BASE;
                    break;
                case BCM4349_CHIP_GRPID:
                    /* RAM based changed from 4349c0(revid=9) onwards */
                    bus->dongle_ram_base = ((bus->sih->chiprev < 0x9)
                                                ? CR4_4349_RAM_BASE
                                                : CR4_4349_RAM_BASE_FROM_REV_9);
                    break;
                case BCM4364_CHIP_ID:
                    bus->dongle_ram_base = CR4_4364_RAM_BASE;
                    break;
                case BCM4362_CHIP_ID:
                    bus->dongle_ram_base = CR4_4362_RAM_BASE;
                    break;
                case BCM43751_CHIP_ID:
                    bus->dongle_ram_base = CR4_43751_RAM_BASE;
                    break;
                case BCM43752_CHIP_ID:
                    bus->dongle_ram_base = CR4_43752_RAM_BASE;
                    break;
                case BCM4369_CHIP_ID:
                    bus->dongle_ram_base = CR4_4369_RAM_BASE;
                    break;
                default:
                    bus->dongle_ram_base = 0;
                    DHD_ERROR(("%s: WARNING: Using default ram base at 0x%x\n",
                               __FUNCTION__, bus->dongle_ram_base));
            }
        }
        bus->ramsize = bus->orig_ramsize;
        if (dhd_dongle_ramsize) {
            dhd_dongle_setramsize(bus, dhd_dongle_ramsize);
        }

        DHD_ERROR(("DHD: dongle ram size is set to %d(orig %d) at 0x%x\n",
                   bus->ramsize, bus->orig_ramsize, bus->dongle_ram_base));

        bus->srmemsize = si_socram_srmem_size(bus->sih);
    }

    /* ...but normally deal with the SDPCMDEV core */
#ifdef BCMSDIOLITE
    if (!(bus->regs = si_setcore(bus->sih, CC_CORE_ID, 0))) {
        DHD_ERROR(("%s: failed to find Chip Common core!\n", __FUNCTION__));
        goto fail;
    }
#else
    if (!(bus->regs = si_setcore(bus->sih, PCMCIA_CORE_ID, 0)) &&
        !(bus->regs = si_setcore(bus->sih, SDIOD_CORE_ID, 0))) {
        DHD_ERROR(("%s: failed to find SDIODEV core!\n", __FUNCTION__));
        goto fail;
    }
#endif // endif
    bus->sdpcmrev = si_corerev(bus->sih);

    /* Set core control so an SDIO reset does a backplane reset */
    OR_REG(osh, &bus->regs->corecontrol, CC_BPRESEN);
#ifndef BCMSPI
    bus->rxint_mode = SDIO_DEVICE_HMB_RXINT;

    if ((bus->sih->buscoretype == SDIOD_CORE_ID) && (bus->sdpcmrev >= 0x4) &&
        (bus->rxint_mode == SDIO_DEVICE_RXDATAINT_MODE_1)) {
        uint32 val;

        val = R_REG(osh, &bus->regs->corecontrol);
        val &= ~CC_XMTDATAAVAIL_MODE;
        val |= CC_XMTDATAAVAIL_CTRL;
        W_REG(osh, &bus->regs->corecontrol, val);
    }
#endif /* BCMSPI */

    pktq_init(&bus->txq, (PRIOMASK + 1), QLEN);

    /* Locate an appropriately-aligned portion of hdrbuf */
#ifndef DYNAMIC_MAX_HDR_READ
    bus->rxhdr = (uint8 *)ROUNDUP((uintptr)&bus->hdrbuf[0], DHD_SDALIGN);
#endif

    /* Set the poll and/or interrupt flags */
    bus->intr = (bool)dhd_intr;
    if ((bus->poll = (bool)dhd_poll)) {
        bus->pollrate = 1;
    }

    /* Setting default Glom size */
    bus->txglomsize = SDPCM_DEFGLOM_SIZE;

    return TRUE;

fail:
    if (bus->sih != NULL) {
        si_detach(bus->sih);
        bus->sih = NULL;
    }
    return FALSE;
}

static bool dhdsdio_probe_malloc(dhd_bus_t *bus, osl_t *osh, void *sdh)
{
    DHD_TRACE(("%s: Enter\n", __FUNCTION__));

    if (bus->dhd->maxctl) {
        bus->rxblen =
            ROUNDUP((bus->dhd->maxctl + SDPCM_HDRLEN), ALIGNMENT) + DHD_SDALIGN;
        if (!(bus->rxbuf =
                  DHD_OS_PREALLOC(bus->dhd, DHD_PREALLOC_RXBUF, bus->rxblen))) {
            DHD_ERROR(("%s: MALLOC of %d-byte rxbuf failed\n", __FUNCTION__,
                       bus->rxblen));
            goto fail;
        }
    }
    /* Allocate buffer to receive glomed packet */
    if (!(bus->databuf =
              DHD_OS_PREALLOC(bus->dhd, DHD_PREALLOC_DATABUF, MAX_DATA_BUF))) {
        DHD_ERROR(("%s: MALLOC of %d-byte databuf failed\n", __FUNCTION__,
                   MAX_DATA_BUF));
        /* release rxbuf which was already located as above */
        if (!bus->rxblen) {
            DHD_OS_PREFREE(bus->dhd, bus->rxbuf, bus->rxblen);
        }
        goto fail;
    }
    /* Allocate buffer to membuf */
    bus->membuf = MALLOC(osh, MAX_MEM_BUF);
    if (bus->membuf == NULL) {
        DHD_ERROR(("%s: MALLOC of %d-byte membuf failed\n", __FUNCTION__,
                   MAX_MEM_BUF));
        if (bus->databuf) {
#ifndef CONFIG_DHD_USE_STATIC_BUF
            MFREE(osh, bus->databuf, MAX_DATA_BUF);
#endif
            bus->databuf = NULL;
        }
        /* release rxbuf which was already located as above */
        if (!bus->rxblen) {
            DHD_OS_PREFREE(bus->dhd, bus->rxbuf, bus->rxblen);
        }
        goto fail;
    }
    memset(bus->membuf, 0, MAX_MEM_BUF);

    /* Align the buffer */
    if ((uintptr)bus->databuf % DHD_SDALIGN) {
        bus->dataptr = bus->databuf +
                       (DHD_SDALIGN - ((uintptr)bus->databuf % DHD_SDALIGN));
    } else {
        bus->dataptr = bus->databuf;
    }

    return TRUE;

fail:
    return FALSE;
}

static bool dhdsdio_probe_init(dhd_bus_t *bus, osl_t *osh, void *sdh)
{
    int32 fnum;

    DHD_TRACE(("%s: Enter\n", __FUNCTION__));

    bus->_srenab = FALSE;

#ifdef SDTEST
    dhdsdio_pktgen_init(bus);
#endif /* SDTEST */

#ifndef BCMSPI
    /* Disable F2 to clear any intermediate frame state on the dongle */
    bcmsdh_cfg_write(sdh, SDIO_FUNC_0, SDIOD_CCCR_IOEN, SDIO_FUNC_ENABLE_1,
                     NULL);
#endif /* !BCMSPI */

    DHD_ERROR(("%s: making DHD_BUS_DOWN\n", __FUNCTION__));
    bus->dhd->busstate = DHD_BUS_DOWN;
    bus->sleeping = FALSE;
    bus->rxflow = FALSE;
    bus->prev_rxlim_hit = 0;

#ifndef BCMSPI
    /* Done with backplane-dependent accesses, can drop clock... */
    bcmsdh_cfg_write(sdh, SDIO_FUNC_1, SBSDIO_FUNC1_CHIPCLKCSR, 0, NULL);
#endif /* !BCMSPI */

    /* ...and initialize clock/power states */
    bus->clkstate = CLK_SDONLY;
    bus->idletime = (int32)dhd_idletime;
    bus->idleclock = DHD_IDLE_ACTIVE;

    /* Query the SD clock speed */
    if (bcmsdh_iovar_op(sdh, "sd_divisor", NULL, 0, &bus->sd_divisor,
                        sizeof(int32), FALSE) != BCME_OK) {
        DHD_ERROR(("%s: fail on %s get\n", __FUNCTION__, "sd_divisor"));
        bus->sd_divisor = -1;
    } else {
        DHD_INFO(("%s: Initial value for %s is %d\n", __FUNCTION__,
                  "sd_divisor", bus->sd_divisor));
    }

    /* Query the SD bus mode */
    if (bcmsdh_iovar_op(sdh, "sd_mode", NULL, 0, &bus->sd_mode, sizeof(int32),
                        FALSE) != BCME_OK) {
        DHD_ERROR(("%s: fail on %s get\n", __FUNCTION__, "sd_mode"));
        bus->sd_mode = -1;
    } else {
        DHD_INFO(("%s: Initial value for %s is %d\n", __FUNCTION__, "sd_mode",
                  bus->sd_mode));
    }

    /* Query the F2 block size, set roundup accordingly */
    fnum = 0x2;
    if (bcmsdh_iovar_op(sdh, "sd_blocksize", &fnum, sizeof(int32),
                        &bus->blocksize, sizeof(int32), FALSE) != BCME_OK) {
        bus->blocksize = 0;
        DHD_ERROR(
            ("%s: fail on fn %d %s get\n", __FUNCTION__, fnum, "sd_blocksize"));
    } else {
        DHD_INFO(("%s: Initial value for fn %d %s is %d\n", __FUNCTION__, fnum,
                  "sd_blocksize", bus->blocksize));

        dhdsdio_tune_fifoparam(bus);
    }
    bus->roundup = MIN(max_roundup, bus->blocksize);

#ifdef DHDENABLE_TAILPAD
    if (bus->pad_pkt) {
        PKTFREE(osh, bus->pad_pkt, FALSE);
    }
    bus->pad_pkt = PKTGET(osh, SDIO_MAX_BLOCK_SIZE, FALSE);
    if (bus->pad_pkt == NULL) {
        DHD_ERROR(("failed to allocate padding packet\n"));
    } else {
        int alignment_offset = 0;
        uintptr pktprt = (uintptr)PKTDATA(osh, bus->pad_pkt);
        if (!(pktprt & 1) && (pktprt = (pktprt % DHD_SDALIGN))) {
            PKTPUSH(osh, bus->pad_pkt, alignment_offset);
        }
        PKTSETNEXT(osh, bus->pad_pkt, NULL);
    }
#endif /* DHDENABLE_TAILPAD */

    /* Query if bus module supports packet chaining, default to use if supported
     */
    if (bcmsdh_iovar_op(sdh, "sd_rxchain", NULL, 0, &bus->sd_rxchain,
                        sizeof(int32), FALSE) != BCME_OK) {
        bus->sd_rxchain = FALSE;
    } else {
        DHD_INFO(("%s: bus module (through bcmsdh API) %s chaining\n",
                  __FUNCTION__,
                  (bus->sd_rxchain ? "supports" : "does not support")));
    }
    bus->use_rxchain = (bool)bus->sd_rxchain;
    bus->txinrx_thres = CUSTOM_TXINRX_THRES;
    /* TX first in dhdsdio_readframes() */
    bus->dotxinrx = TRUE;

#ifdef PKT_STATICS
    dhd_bus_clear_txpktstatics(bus->dhd);
#endif

    return TRUE;
}

int dhd_bus_download_firmware(struct dhd_bus *bus, osl_t *osh, char *pfw_path,
                              char *pnv_path, char *pclm_path, char *pconf_path)
{
    int ret;

    bus->fw_path = pfw_path;
    bus->nv_path = pnv_path;
    bus->dhd->clm_path = pclm_path;
    bus->dhd->conf_path = pconf_path;

    ret = dhdsdio_download_firmware(bus, osh, bus->sdh);

    return ret;
}

int dhd_set_bus_params(struct dhd_bus *bus)
{
    int ret = 0;

    if (bus->dhd->conf->dhd_poll >= 0) {
        bus->poll = bus->dhd->conf->dhd_poll;
        if (!bus->pollrate) {
            bus->pollrate = 1;
        }
        printf("%s: set polling mode %d\n", __FUNCTION__,
               bus->dhd->conf->dhd_poll);
    }
    if (bus->dhd->conf->use_rxchain >= 0) {
        bus->use_rxchain = (bool)bus->dhd->conf->use_rxchain;
    }
    if (bus->dhd->conf->txinrx_thres >= 0) {
        bus->txinrx_thres = bus->dhd->conf->txinrx_thres;
    }
    if (bus->dhd->conf->txglomsize >= 0) {
        bus->txglomsize = bus->dhd->conf->txglomsize;
    }
#ifdef MINIME
    if (bus->dhd->conf->fw_type == FW_TYPE_MINIME) {
        bus->ramsize = bus->dhd->conf->ramsize;
        printf("%s: set ramsize 0x%x\n", __FUNCTION__, bus->ramsize);
    }
#endif
#ifdef DYNAMIC_MAX_HDR_READ
    if (bus->dhd->conf->max_hdr_read <= 0) {
        bus->dhd->conf->max_hdr_read = MAX_HDR_READ;
    }
    if (bus->hdrbufp) {
        MFREE(bus->dhd->osh, bus->hdrbufp,
              bus->dhd->conf->max_hdr_read + DHD_SDALIGN);
    }
    bus->hdrbufp =
        MALLOC(bus->dhd->osh, bus->dhd->conf->max_hdr_read + DHD_SDALIGN);
    if (bus->hdrbufp == NULL) {
        DHD_ERROR(("%s: MALLOC of %d-byte hdrbufp failed\n", __FUNCTION__,
                   bus->dhd->conf->max_hdr_read + DHD_SDALIGN));
        ret = -1;
        goto exit;
    }
    bus->rxhdr = (uint8 *)ROUNDUP((uintptr)bus->hdrbufp, DHD_SDALIGN);

exit:
#endif
    return ret;
}

static int dhdsdio_download_firmware(struct dhd_bus *bus, osl_t *osh, void *sdh)
{
    int ret;

#if defined(SUPPORT_MULTIPLE_REVISION)
    if (concate_revision(bus, bus->fw_path, bus->nv_path) != 0) {
        DHD_ERROR(("%s: fail to concatnate revison \n", __FUNCTION__));
        return BCME_BADARG;
    }
#endif /* SUPPORT_MULTIPLE_REVISION */

#if defined(DHD_BLOB_EXISTENCE_CHECK)
    dhd_set_blob_support(bus->dhd, bus->fw_path);
#endif /* DHD_BLOB_EXISTENCE_CHECK */

    DHD_TRACE_HW4(("%s: firmware path=%s, nvram path=%s\n", __FUNCTION__,
                   bus->fw_path, bus->nv_path));
    DHD_OS_WAKE_LOCK(bus->dhd);

    dhd_conf_set_path_params(bus->dhd, bus->fw_path, bus->nv_path);
    ret = dhd_set_bus_params(bus);
    if (ret) {
        goto exit;
    }

    /* Download the firmware */
    dhdsdio_clkctl(bus, CLK_AVAIL, FALSE);

    ret = _dhdsdio_download_firmware(bus);

    dhdsdio_clkctl(bus, CLK_SDONLY, FALSE);

exit:
    DHD_OS_WAKE_UNLOCK(bus->dhd);
    return ret;
}

/* Detach and free everything */
static void dhdsdio_release(dhd_bus_t *bus, osl_t *osh)
{
    bool dongle_isolation = FALSE;
    DHD_TRACE(("%s: Enter\n", __FUNCTION__));

    if (bus) {
        ASSERT(osh);

        if (bus->dhd) {
#if defined(DEBUGGER) || defined(DHD_DSCOPE)
            debugger_close();
#endif /* DEBUGGER || DHD_DSCOPE */
            dongle_isolation = bus->dhd->dongle_isolation;
            dhd_detach(bus->dhd);
        }

        /* De-register interrupt handler */
        bcmsdh_intr_disable(bus->sdh);
        bcmsdh_intr_dereg(bus->sdh);

        if (bus->dhd) {
            dhdsdio_release_dongle(bus, osh, dongle_isolation, TRUE);
            dhd_free(bus->dhd);
            bus->dhd = NULL;
        }

        dhdsdio_release_malloc(bus, osh);

#ifdef DHD_DEBUG
        if (bus->console.buf != NULL) {
            MFREE(osh, bus->console.buf, bus->console.bufsize);
        }
#endif // endif

#ifdef DHDENABLE_TAILPAD
        if (bus->pad_pkt) {
            PKTFREE(osh, bus->pad_pkt, FALSE);
        }
#endif /* DHDENABLE_TAILPAD */
#ifdef DYNAMIC_MAX_HDR_READ
        if (bus->hdrbufp) {
            MFREE(osh, bus->hdrbufp, MAX_HDR_READ + DHD_SDALIGN);
        }
#endif

        MFREE(osh, bus, sizeof(dhd_bus_t));
    }

    DHD_TRACE(("%s: Disconnected\n", __FUNCTION__));
}

static void dhdsdio_release_malloc(dhd_bus_t *bus, osl_t *osh)
{
    DHD_TRACE(("%s: Enter\n", __FUNCTION__));

    if (bus->dhd && bus->dhd->dongle_reset) {
        return;
    }

    if (bus->rxbuf) {
#ifndef CONFIG_DHD_USE_STATIC_BUF
        MFREE(osh, bus->rxbuf, bus->rxblen);
#endif // endif
        bus->rxctl = bus->rxbuf = NULL;
        bus->rxlen = 0;
    }

    if (bus->databuf) {
#ifndef CONFIG_DHD_USE_STATIC_BUF
        MFREE(osh, bus->databuf, MAX_DATA_BUF);
#endif // endif
        bus->databuf = NULL;
    }

    if (bus->membuf) {
        MFREE(osh, bus->membuf, MAX_MEM_BUF);
        bus->membuf = NULL;
    }

    if (bus->vars && bus->varsz) {
        MFREE(osh, bus->vars, bus->varsz);
        bus->vars = NULL;
    }
}

static void dhdsdio_release_dongle(dhd_bus_t *bus, osl_t *osh,
                                   bool dongle_isolation, bool reset_flag)
{
    DHD_TRACE(("%s: Enter bus->dhd %p bus->dhd->dongle_reset %d \n",
               __FUNCTION__, bus->dhd, bus->dhd->dongle_reset));

    if ((bus->dhd && bus->dhd->dongle_reset) && reset_flag) {
        return;
    }

    if (bus->sih) {
        /* In Win10, system will be BSOD if using "sysprep" to do OS image */
        /* Skip this will not cause the BSOD. */
#if !defined(BCMLXSDMMC)
        if (bus->dhd) {
            dhdsdio_clkctl(bus, CLK_AVAIL, FALSE);
        }
        if (KSO_ENAB(bus) && (dongle_isolation == FALSE)) {
            si_watchdog(bus->sih, 0x4);
        }
#endif /* !defined(BCMLXSDMMC) */
        if (bus->dhd) {
            dhdsdio_clkctl(bus, CLK_NONE, FALSE);
        }
        si_detach(bus->sih);
        bus->sih = NULL;
        if (bus->vars && bus->varsz) {
            MFREE(osh, bus->vars, bus->varsz);
        }
        bus->vars = NULL;
    }

    DHD_TRACE(("%s: Disconnected\n", __FUNCTION__));
}

static void dhdsdio_disconnect(void *ptr)
{
    dhd_bus_t *bus = (dhd_bus_t *)ptr;

    DHD_TRACE(("%s: Enter\n", __FUNCTION__));

    DHD_MUTEX_LOCK();
    if (bus) {
        ASSERT(bus->dhd);
        /* Advertise bus remove during rmmod */
        dhdsdio_advertise_bus_remove(bus->dhd);
        dhdsdio_release(bus, bus->dhd->osh);
    }
    DHD_MUTEX_UNLOCK();

    DHD_TRACE(("%s: Disconnected\n", __FUNCTION__));
}

static int dhdsdio_suspend(void *context)
{
    int ret = 0;
#ifdef SUPPORT_P2P_GO_PS
    int wait_time = 0;
#endif /* SUPPORT_P2P_GO_PS */

    dhd_bus_t *bus = (dhd_bus_t *)context;
    unsigned long flags;

    DHD_ERROR(("%s Enter\n", __FUNCTION__));
    if (bus->dhd == NULL) {
        DHD_ERROR(("bus not inited\n"));
        return BCME_ERROR;
    }
    if (bus->dhd->prot == NULL) {
        DHD_ERROR(("prot is not inited\n"));
        return BCME_ERROR;
    }

    if (bus->dhd->up == FALSE) {
        return BCME_OK;
    }

    DHD_LINUX_GENERAL_LOCK(bus->dhd, flags);
    if (bus->dhd->busstate != DHD_BUS_DATA &&
        bus->dhd->busstate != DHD_BUS_SUSPEND) {
        DHD_ERROR(("not in a readystate to LPBK  is not inited\n"));
        DHD_LINUX_GENERAL_UNLOCK(bus->dhd, flags);
        return BCME_ERROR;
    }
    DHD_LINUX_GENERAL_UNLOCK(bus->dhd, flags);
    if (bus->dhd->dongle_reset) {
        DHD_ERROR(("Dongle is in reset state.\n"));
        return -EIO;
    }

    DHD_LINUX_GENERAL_LOCK(bus->dhd, flags);
    /* stop all interface network queue. */
    dhd_txflowcontrol(bus->dhd, ALL_INTERFACES, ON);
    bus->dhd->busstate = DHD_BUS_SUSPEND;
    if (DHD_BUS_BUSY_CHECK_IN_TX(bus->dhd)) {
        DHD_ERROR(("Tx Request is not ended\n"));
        bus->dhd->busstate = DHD_BUS_DATA;
        /* resume all interface network queue. */
        dhd_txflowcontrol(bus->dhd, ALL_INTERFACES, OFF);
        DHD_LINUX_GENERAL_UNLOCK(bus->dhd, flags);
        return -EBUSY;
    }
    DHD_BUS_BUSY_SET_SUSPEND_IN_PROGRESS(bus->dhd);
    DHD_LINUX_GENERAL_UNLOCK(bus->dhd, flags);

#ifdef SUPPORT_P2P_GO_PS
    if (bus->idletime > 0) {
        wait_time = msecs_to_jiffies(bus->idletime * dhd_watchdog_ms);
    }
#endif /* SUPPORT_P2P_GO_PS */
    ret = dhd_os_check_wakelock(bus->dhd);
#ifdef SUPPORT_P2P_GO_PS
    // terence 20141124: fix for suspend issue
    if (SLPAUTO_ENAB(bus) && (!ret) && (bus->dhd->up) &&
        (bus->dhd->op_mode != DHD_FLAG_HOSTAP_MODE)) {
        if (wait_event_timeout(bus->bus_sleep, bus->sleeping, wait_time) == 0) {
            if (!bus->sleeping) {
                ret = 1;
            }
        }
    }
#endif /* SUPPORT_P2P_GO_PS */

    DHD_LINUX_GENERAL_LOCK(bus->dhd, flags);
    if (ret) {
        bus->dhd->busstate = DHD_BUS_DATA;
        /* resume all interface network queue. */
        dhd_txflowcontrol(bus->dhd, ALL_INTERFACES, OFF);
    }
    DHD_BUS_BUSY_CLEAR_SUSPEND_IN_PROGRESS(bus->dhd);
    dhd_os_busbusy_wake(bus->dhd);
    DHD_LINUX_GENERAL_UNLOCK(bus->dhd, flags);
    return ret;
}

static int dhdsdio_resume(void *context)
{
    dhd_bus_t *bus = (dhd_bus_t *)context;
    ulong flags;

    DHD_ERROR(("%s Enter\n", __FUNCTION__));

    if (bus->dhd->up == FALSE) {
        return BCME_OK;
    }

    DHD_LINUX_GENERAL_LOCK(bus->dhd, flags);
    DHD_BUS_BUSY_SET_RESUME_IN_PROGRESS(bus->dhd);
    DHD_LINUX_GENERAL_UNLOCK(bus->dhd, flags);

#if defined(OOB_INTR_ONLY)
    if (dhd_os_check_if_up(bus->dhd)) {
        bcmsdh_oob_intr_set(bus->sdh, TRUE);
    }
#endif /* defined(OOB_INTR_ONLY) */

    DHD_LINUX_GENERAL_LOCK(bus->dhd, flags);
    DHD_BUS_BUSY_CLEAR_RESUME_IN_PROGRESS(bus->dhd);
    bus->dhd->busstate = DHD_BUS_DATA;
    dhd_os_busbusy_wake(bus->dhd);
    /* resume all interface network queue. */
    dhd_txflowcontrol(bus->dhd, ALL_INTERFACES, OFF);
    DHD_LINUX_GENERAL_UNLOCK(bus->dhd, flags);

    return 0;
}

/* Register/Unregister functions are called by the main DHD entry
 * point (e.g. module insertion) to link with the bus driver, in
 * order to look for or await the device.
 */

static bcmsdh_driver_t dhd_sdio = {dhdsdio_probe, dhdsdio_disconnect,
                                   dhdsdio_suspend, dhdsdio_resume};

int dhd_bus_register(void)
{
    DHD_TRACE(("%s: Enter\n", __FUNCTION__));

    return bcmsdh_register(&dhd_sdio);
}

void dhd_bus_unregister(void)
{
    DHD_TRACE(("%s: Enter\n", __FUNCTION__));

    bcmsdh_unregister();
}

#if defined(BCMLXSDMMC)
/* Register a dummy SDIO client driver in order to be notified of new SDIO
 * device */
int dhd_bus_reg_sdio_notify(void *semaphore)
{
    return bcmsdh_reg_sdio_notify(semaphore);
}

void dhd_bus_unreg_sdio_notify(void)
{
    bcmsdh_unreg_sdio_notify();
}
#endif /* defined(BCMLXSDMMC) */

static int dhdsdio_download_code_file(struct dhd_bus *bus, char *pfw_path)
{
    int bcmerror = -1;
    int offset = 0;
    int len;
    void *image = NULL;
    uint8 *memblock = NULL, *memptr;
#ifdef CHECK_DOWNLOAD_FW
    uint8 *memptr_tmp = NULL; // terence: check downloaded firmware is correct
#endif
    uint memblock_size = MEMBLOCK;
#ifdef DHD_DEBUG_DOWNLOADTIME
    unsigned long initial_jiffies = 0;
    uint firmware_sz = 0;
#endif // endif

    DHD_INFO(("%s: download firmware %s\n", __FUNCTION__, pfw_path));

    image = dhd_os_open_image1(bus->dhd, pfw_path);
    if (image == NULL) {
        printf("%s: Open firmware file failed %s\n", __FUNCTION__, pfw_path);
        goto err;
    }

    /* Update the dongle image download block size depending on the F1 block
     * size */
    if (sd_f1_blocksize == 0x200) {
        memblock_size = MAX_MEMBLOCK;
    }
    memptr = memblock = MALLOC(bus->dhd->osh, memblock_size + DHD_SDALIGN);
    if (memblock == NULL) {
        DHD_ERROR(("%s: Failed to allocate memory %d bytes\n", __FUNCTION__,
                   memblock_size));
        goto err;
    }
#ifdef CHECK_DOWNLOAD_FW
    if (bus->dhd->conf->fwchk) {
        memptr_tmp = MALLOC(bus->dhd->osh, MEMBLOCK + DHD_SDALIGN);
        if (memptr_tmp == NULL) {
            DHD_ERROR(("%s: Failed to allocate memory %d bytes\n", __FUNCTION__,
                       MEMBLOCK));
            goto err;
        }
    }
#endif
    if ((uint32)(uintptr)memblock % DHD_SDALIGN) {
        memptr += (DHD_SDALIGN - ((uint32)(uintptr)memblock % DHD_SDALIGN));
    }

#ifdef DHD_DEBUG_DOWNLOADTIME
    initial_jiffies = jiffies;
#endif // endif

    /* Download image */
    while (
        (len = dhd_os_get_image_block((char *)memptr, memblock_size, image))) {
        // terence 20150412: fix for firmware failed to download
        if (bus->dhd->conf->chip == BCM43340_CHIP_ID ||
            bus->dhd->conf->chip == BCM43341_CHIP_ID) {
            if (len % 0x40 != 0) {
                memset(memptr + len, 0, len % 0x40);
                len += (0x40 - len % 0x40);
            }
        }
        if (len < 0) {
            DHD_ERROR(("%s: dhd_os_get_image_block failed (%d)\n", __FUNCTION__,
                       len));
            bcmerror = BCME_ERROR;
            goto err;
        }
        /* check if CR4 */
        if (si_setcore(bus->sih, ARMCR4_CORE_ID, 0)) {
            /* if address is 0, store the reset instruction to be written in 0
             */

            if (offset == 0) {
                bus->resetinstr = *(((uint32 *)memptr));
                /* Add start of RAM address to the address given by user */
                offset += bus->dongle_ram_base;
            }
        }

        bcmerror = dhdsdio_membytes(bus, TRUE, offset, memptr, len);
        if (bcmerror) {
            DHD_ERROR(("%s: error %d on writing %d membytes at 0x%08x\n",
                       __FUNCTION__, bcmerror, memblock_size, offset));
            goto err;
        }

#ifdef CHECK_DOWNLOAD_FW
        if (bus->dhd->conf->fwchk) {
            bcmerror = dhdsdio_membytes(bus, FALSE, offset, memptr_tmp, len);
            if (bcmerror) {
                DHD_ERROR(("%s: error %d on reading %d membytes at 0x%08x\n",
                           __FUNCTION__, bcmerror, MEMBLOCK, offset));
                goto err;
            }
            if (memcmp(memptr_tmp, memptr, len)) {
                DHD_ERROR(("%s: Downloaded image is corrupted at 0x%08x\n",
                           __FUNCTION__, offset));
                bcmerror = BCME_ERROR;
                goto err;
            } else {
                DHD_INFO(("%s: Download, Upload and compare succeeded.\n",
                          __FUNCTION__));
            }
        }
#endif

        offset += memblock_size;
#ifdef DHD_DEBUG_DOWNLOADTIME
        firmware_sz += len;
#endif // endif
    }

#ifdef DHD_DEBUG_DOWNLOADTIME
    DHD_ERROR(("Firmware download time for %u bytes: %u ms\n", firmware_sz,
               jiffies_to_msecs(jiffies - initial_jiffies)));
#endif // endif

err:
    if (memblock) {
        MFREE(bus->dhd->osh, memblock, memblock_size + DHD_SDALIGN);
    }
#ifdef CHECK_DOWNLOAD_FW
    if (bus->dhd->conf->fwchk) {
        if (memptr_tmp) {
            MFREE(bus->dhd->osh, memptr_tmp, MEMBLOCK + DHD_SDALIGN);
        }
    }
#endif

    if (image) {
        dhd_os_close_image1(bus->dhd, image);
    }

    return bcmerror;
}

#ifdef DHD_UCODE_DOWNLOAD
/* Currently supported only for the chips in which ucode RAM is AXI addressable
 */
static uint32 dhdsdio_ucode_base(struct dhd_bus *bus)
{
    uint32 ucode_base = 0;

    switch ((uint16)bus->sih->chip) {
        case BCM43012_CHIP_ID:
            ucode_base = 0xE8020000;
            break;
        default:
            DHD_ERROR(("%s: Unsupported!\n", __func__));
            break;
    }

    return ucode_base;
}

static int dhdsdio_download_ucode_file(struct dhd_bus *bus, char *ucode_path)
{
    int bcmerror = -1;
    int offset = 0;
    int len;
    uint32 ucode_base;
    void *image = NULL;
    uint8 *memblock = NULL, *memptr;
    uint memblock_size = MEMBLOCK;
#ifdef DHD_DEBUG_DOWNLOADTIME
    unsigned long initial_jiffies = 0;
    uint firmware_sz = 0;
#endif // endif

    DHD_INFO(("%s: download firmware %s\n", __FUNCTION__, ucode_path));

    ucode_base = dhdsdio_ucode_base(bus);

    image = dhd_os_open_image1(bus->dhd, ucode_path);
    if (image == NULL) {
        goto err;
    }

    /* Update the dongle image download block size depending on the F1 block
     * size */
    if (sd_f1_blocksize == 0x200) {
        memblock_size = MAX_MEMBLOCK;
    }

    memptr = memblock = MALLOC(bus->dhd->osh, memblock_size + DHD_SDALIGN);
    if (memblock == NULL) {
        DHD_ERROR(("%s: Failed to allocate memory %d bytes\n", __FUNCTION__,
                   memblock_size));
        goto err;
    }
    if ((uint32)(uintptr)memblock % DHD_SDALIGN) {
        memptr += (DHD_SDALIGN - ((uint32)(uintptr)memblock % DHD_SDALIGN));
    }

#ifdef DHD_DEBUG_DOWNLOADTIME
    initial_jiffies = jiffies;
#endif // endif

    /* Download image */
    while (
        (len = dhd_os_get_image_block((char *)memptr, memblock_size, image))) {
        if (len < 0) {
            DHD_ERROR(("%s: dhd_os_get_image_block failed (%d)\n", __FUNCTION__,
                       len));
            bcmerror = BCME_ERROR;
            goto err;
        }

        bcmerror =
            dhdsdio_membytes(bus, TRUE, (ucode_base + offset), memptr, len);
        if (bcmerror) {
            DHD_ERROR(("%s: error %d on writing %d membytes at 0x%08x\n",
                       __FUNCTION__, bcmerror, memblock_size, offset));
            goto err;
        }

        offset += memblock_size;
#ifdef DHD_DEBUG_DOWNLOADTIME
        firmware_sz += len;
#endif // endif
    }

#ifdef DHD_DEBUG_DOWNLOADTIME
    DHD_ERROR(("ucode download time for %u bytes: %u ms\n", firmware_sz,
               jiffies_to_msecs(jiffies - initial_jiffies)));
#endif // endif

err:
    if (memblock) {
        MFREE(bus->dhd->osh, memblock, memblock_size + DHD_SDALIGN);
    }

    if (image) {
        dhd_os_close_image1(bus->dhd, image);
    }

    return bcmerror;
} /* dhdsdio_download_ucode_file */

void dhd_bus_ucode_download(struct dhd_bus *bus)
{
    uint32 shaddr = 0, shdata = 0;

    shaddr = bus->dongle_ram_base + bus->ramsize - 0x4;
    dhdsdio_membytes(bus, FALSE, shaddr, (uint8 *)&shdata, 0x4);

    DHD_TRACE(("%s: shdata:[0x%08x :0x%08x]\n", __func__, shaddr, shdata));

    if (shdata == UCODE_DOWNLOAD_REQUEST) {
        DHD_ERROR(("%s: Received ucode download request!\n", __func__));

        /* Download the ucode */
        if (!dhd_get_ucode_path(bus->dhd)) {
            DHD_ERROR(("%s: bus->uc_path not set!\n", __func__));
            return;
        }
        dhdsdio_download_ucode_file(bus, dhd_get_ucode_path(bus->dhd));

        DHD_ERROR(("%s: Ucode downloaded successfully!\n", __func__));

        shdata = UCODE_DOWNLOAD_COMPLETE;
        dhdsdio_membytes(bus, TRUE, shaddr, (uint8 *)&shdata, 0x4);
    }
}

#endif /* DHD_UCODE_DOWNLOAD */

static int dhdsdio_download_nvram(struct dhd_bus *bus)
{
    int bcmerror = -1;
    uint len;
    void *image = NULL;
    char *memblock = NULL;
    char *bufp;
    char *pnv_path;
    bool nvram_file_exists;

    pnv_path = bus->nv_path;

    nvram_file_exists = ((pnv_path != NULL) && (pnv_path[0] != '\0'));

    /* For Get nvram from UEFI */
    if (nvram_file_exists) {
        image = dhd_os_open_image1(bus->dhd, pnv_path);
        if (image == NULL) {
            printf("%s: Open nvram file failed %s\n", __FUNCTION__, pnv_path);
            goto err;
        }
    }

    memblock = MALLOC(bus->dhd->osh, MAX_NVRAMBUF_SIZE);
    if (memblock == NULL) {
        DHD_ERROR(("%s: Failed to allocate memory %d bytes\n", __FUNCTION__,
                   MAX_NVRAMBUF_SIZE));
        goto err;
    }

    /* For Get nvram from image or UEFI (when image == NULL ) */
    len = dhd_os_get_image_block(memblock, MAX_NVRAMBUF_SIZE, image);
    if (len > 0 && len < MAX_NVRAMBUF_SIZE) {
        bufp = (char *)memblock;
        bufp[len] = 0;
        len = process_nvram_vars(bufp, len);
        if (len % 0x4) {
            len += 0x4 - (len % 0x4);
        }
        bufp += len;
        *bufp++ = 0;
        if (len) {
            bcmerror = dhdsdio_downloadvars(bus, memblock, len + 1);
        }
        if (bcmerror) {
            DHD_ERROR(
                ("%s: error downloading vars: %d\n", __FUNCTION__, bcmerror));
        }
    } else {
        DHD_ERROR(("%s: error reading nvram file: %d\n", __FUNCTION__, len));
        bcmerror = BCME_SDIO_ERROR;
    }

err:
    if (memblock) {
        MFREE(bus->dhd->osh, memblock, MAX_NVRAMBUF_SIZE);
    }

    if (image) {
        dhd_os_close_image1(bus->dhd, image);
    }

    return bcmerror;
}

static int _dhdsdio_download_firmware(struct dhd_bus *bus)
{
    int bcmerror = -1;

    bool embed = FALSE; /* download embedded firmware */
    bool dlok = FALSE;  /* download firmware succeeded */

    /* Out immediately if no image to download */
    if ((bus->fw_path == NULL) || (bus->fw_path[0] == '\0')) {
        return bcmerror;
    }

    /* Keep arm in reset */
    if (dhdsdio_download_state(bus, TRUE)) {
        DHD_ERROR(("%s: error placing ARM core in reset\n", __FUNCTION__));
        goto err;
    }

    /* External image takes precedence if specified */
    if ((bus->fw_path != NULL) && (bus->fw_path[0] != '\0')) {
        if (dhdsdio_download_code_file(bus, bus->fw_path)) {
            DHD_ERROR(
                ("%s: dongle image file download failed\n", __FUNCTION__));
            goto err;
        } else {
            embed = FALSE;
            dlok = TRUE;
        }
    }

    BCM_REFERENCE(embed);
    if (!dlok) {
        DHD_ERROR(("%s: dongle image download failed\n", __FUNCTION__));
        goto err;
    }

    /* External nvram takes precedence if specified */
    if (dhdsdio_download_nvram(bus)) {
        DHD_ERROR(("%s: dongle nvram file download failed\n", __FUNCTION__));
        goto err;
    }

    /* Take arm out of reset */
    if (dhdsdio_download_state(bus, FALSE)) {
        DHD_ERROR(("%s: error getting out of ARM core reset\n", __FUNCTION__));
        goto err;
    }

    bcmerror = 0;

err:
    return bcmerror;
}

static int dhd_bcmsdh_recv_buf(dhd_bus_t *bus, uint32 addr, uint fn, uint flags,
                               uint8 *buf, uint nbytes, void *pkt,
                               bcmsdh_cmplt_fn_t complete_fn, void *handle)
{
    int status;

    if (!KSO_ENAB(bus)) {
        DHD_ERROR(("%s: Device asleep\n", __FUNCTION__));
        return BCME_NODEVICE;
    }

    status = bcmsdh_recv_buf(bus->sdh, addr, fn, flags, buf, nbytes, pkt,
                             complete_fn, handle);

    return status;
}

static int dhd_bcmsdh_send_buf(dhd_bus_t *bus, uint32 addr, uint fn, uint flags,
                               uint8 *buf, uint nbytes, void *pkt,
                               bcmsdh_cmplt_fn_t complete_fn, void *handle,
                               int max_retry)
{
    int ret;
    int i = 0;
    int retries = 0;
    bcmsdh_info_t *sdh;

    if (!KSO_ENAB(bus)) {
        DHD_ERROR(("%s: Device asleep\n", __FUNCTION__));
        return BCME_NODEVICE;
    }

    sdh = bus->sdh;
    do {
        ret = bcmsdh_send_buf(bus->sdh, addr, fn, flags, buf, nbytes, pkt,
                              complete_fn, handle);

        bus->f2txdata++;
        ASSERT(ret != BCME_PENDING);

        if (ret == BCME_NODEVICE) {
            DHD_ERROR(("%s: Device asleep already\n", __FUNCTION__));
        } else if (ret < 0) {
            /* On failure, abort the command and terminate the frame */
            DHD_ERROR(
                ("%s: sdio error %d, abort command and terminate frame.\n",
                 __FUNCTION__, ret));
            bus->tx_sderrs++;
            bus->f1regdata++;
            bus->dhd->tx_errors++;
            bcmsdh_abort(sdh, SDIO_FUNC_2);
            bcmsdh_cfg_write(sdh, SDIO_FUNC_1, SBSDIO_FUNC1_FRAMECTRL,
                             SFC_WF_TERM, NULL);
            for (i = 0; i < READ_FRM_CNT_RETRIES; i++) {
                uint8 hi, lo;
                hi = bcmsdh_cfg_read(sdh, SDIO_FUNC_1, SBSDIO_FUNC1_WFRAMEBCHI,
                                     NULL);
                lo = bcmsdh_cfg_read(sdh, SDIO_FUNC_1, SBSDIO_FUNC1_WFRAMEBCLO,
                                     NULL);
                bus->f1regdata += 0x2;
                if ((hi == 0) && (lo == 0)) {
                    break;
                }
            }
        }
    } while ((ret < 0) && retrydata && ++retries < max_retry);

    return ret;
}

uint8 dhd_bus_is_ioready(struct dhd_bus *bus)
{
    uint8 enable;
    bcmsdh_info_t *sdh;
    ASSERT(bus);
    ASSERT(bus->sih != NULL);
    enable = (SDIO_FUNC_ENABLE_1 | SDIO_FUNC_ENABLE_2);
    sdh = bus->sdh;
    return (enable ==
            bcmsdh_cfg_read(sdh, SDIO_FUNC_0, SDIOD_CCCR_IORDY, NULL));
}

uint dhd_bus_chip(struct dhd_bus *bus)
{
    ASSERT(bus->sih != NULL);
    return bus->sih->chip;
}

uint dhd_bus_chiprev(struct dhd_bus *bus)
{
    ASSERT(bus);
    ASSERT(bus->sih != NULL);
    return bus->sih->chiprev;
}

void *dhd_bus_pub(struct dhd_bus *bus)
{
    return bus->dhd;
}

void *dhd_bus_sih(struct dhd_bus *bus)
{
    return (void *)bus->sih;
}

void *dhd_bus_txq(struct dhd_bus *bus)
{
    return &bus->txq;
}

uint dhd_bus_hdrlen(struct dhd_bus *bus)
{
    return (bus->txglom_enable) ? SDPCM_HDRLEN_TXGLOM : SDPCM_HDRLEN;
}

void dhd_bus_set_dotxinrx(struct dhd_bus *bus, bool val)
{
    bus->dotxinrx = val;
}

/*
 *  dhdsdio_advertise_bus_cleanup advertises that clean up is under progress
 * to other bus user contexts like Tx, Rx, IOVAR, WD etc and it waits for other
 * contexts to gracefully exit. All the bus usage contexts before marking
 * busstate as busy, will check for whether the busstate is DHD_BUS_DOWN or
 * DHD_BUS_DOWN_IN_PROGRESS, if so they will exit from there itself without
 * marking dhd_bus_busy_state as BUSY.
 */
static void dhdsdio_advertise_bus_cleanup(dhd_pub_t *dhdp)
{
    unsigned long flags;
    int timeleft;

    DHD_LINUX_GENERAL_LOCK(dhdp, flags);
    dhdp->busstate = DHD_BUS_DOWN_IN_PROGRESS;
    DHD_LINUX_GENERAL_UNLOCK(dhdp, flags);

    timeleft = dhd_os_busbusy_wait_negation(dhdp, &dhdp->dhd_bus_busy_state);
    if ((timeleft == 0) || (timeleft == 1)) {
        DHD_ERROR(("%s : Timeout due to dhd_bus_busy_state=0x%x\n",
                   __FUNCTION__, dhdp->dhd_bus_busy_state));
        ASSERT(0);
    }

    return;
}

static void dhdsdio_advertise_bus_remove(dhd_pub_t *dhdp)
{
    unsigned long flags;
    int timeleft;

    DHD_LINUX_GENERAL_LOCK(dhdp, flags);
    dhdp->busstate = DHD_BUS_REMOVE;
    DHD_LINUX_GENERAL_UNLOCK(dhdp, flags);

    timeleft = dhd_os_busbusy_wait_negation(dhdp, &dhdp->dhd_bus_busy_state);
    if ((timeleft == 0) || (timeleft == 1)) {
        DHD_ERROR(("%s : Timeout due to dhd_bus_busy_state=0x%x\n",
                   __FUNCTION__, dhdp->dhd_bus_busy_state));
        ASSERT(0);
    }

    return;
}

int dhd_bus_devreset(dhd_pub_t *dhdp, uint8 flag)
{
    int bcmerror = 0;
    dhd_bus_t *bus;
    unsigned long flags;

    bus = dhdp->bus;

    if (flag == TRUE) {
        if (!bus->dhd->dongle_reset) {
            DHD_ERROR(("%s: == Power OFF ==\n", __FUNCTION__));
            dhdsdio_advertise_bus_cleanup(bus->dhd);
            dhd_os_sdlock(dhdp);
            dhd_os_wd_timer(dhdp, 0);
#if !defined(IGNORE_ETH0_DOWN)
            /* Force flow control as protection when stop come before
             * ifconfig_down */
            dhd_txflowcontrol(bus->dhd, ALL_INTERFACES, ON);
#endif /* !defined(IGNORE_ETH0_DOWN) */
            /* Expect app to have torn down any connection before calling */
            /* Stop the bus, disable F2 */
            dhd_bus_stop(bus, FALSE);
#if defined(OOB_INTR_ONLY)
            /* Clean up any pending IRQ */
            dhd_enable_oob_intr(bus, FALSE);
            bcmsdh_oob_intr_set(bus->sdh, FALSE);
            bcmsdh_oob_intr_unregister(bus->sdh);
#endif /* defined(OOB_INTR_ONLY) */

            /* Clean tx/rx buffer pointers, detach from the dongle */
            dhdsdio_release_dongle(bus, bus->dhd->osh, TRUE, TRUE);

            bus->dhd->dongle_reset = TRUE;
            DHD_ERROR(("%s: making dhdpub up FALSE\n", __FUNCTION__));
            bus->dhd->up = FALSE;
            dhd_txglom_enable(dhdp, FALSE);
            dhd_os_sdunlock(dhdp);

            DHD_LINUX_GENERAL_LOCK(bus->dhd, flags);
            DHD_ERROR(("%s: making DHD_BUS_DOWN\n", __FUNCTION__));
            bus->dhd->busstate = DHD_BUS_DOWN;
            DHD_LINUX_GENERAL_UNLOCK(bus->dhd, flags);

            printf("%s:  WLAN OFF DONE\n", __FUNCTION__);
            /* App can now remove power from device */
        } else {
            bcmerror = BCME_SDIO_ERROR;
        }
    } else {
        /* App must have restored power to device before calling */

        printf("%s: == Power ON ==\n", __FUNCTION__);

        if (bus->dhd->dongle_reset) {
            /* Turn on WLAN */
            dhd_os_sdlock(dhdp);
            /* Reset SD client -- required if devreset is called
             * via 'dhd devreset' iovar
             */
            bcmsdh_reset(bus->sdh);
            /* Attempt to re-attach & download */
            if (dhdsdio_probe_attach(
                    bus, bus->dhd->osh, bus->sdh,
                    (uint32 *)(uintptr)si_enum_base(bus->cl_devid),
                    bus->cl_devid)) {
                DHD_LINUX_GENERAL_LOCK(bus->dhd, flags);
                DHD_ERROR(("%s: making DHD_BUS_DOWN\n", __FUNCTION__));
                bus->dhd->busstate = DHD_BUS_DOWN;
                DHD_LINUX_GENERAL_UNLOCK(bus->dhd, flags);

                /* Attempt to download binary to the dongle */
                if (dhdsdio_probe_init(bus, bus->dhd->osh, bus->sdh) &&
                    dhdsdio_download_firmware(bus, bus->dhd->osh, bus->sdh) >= 0) {
                    /* Re-init bus, enable F2 transfer */
                    bcmerror = dhd_bus_init((dhd_pub_t *)bus->dhd, FALSE);
                    if (bcmerror == BCME_OK) {
#if defined(OOB_INTR_ONLY)
                        dhd_enable_oob_intr(bus, TRUE);
                        bcmsdh_oob_intr_register(bus->sdh, dhdsdio_isr, bus);
                        bcmsdh_oob_intr_set(bus->sdh, TRUE);
#elif defined(FORCE_WOWLAN)
                        dhd_enable_oob_intr(bus, TRUE);
#endif /* defined(OOB_INTR_ONLY) */

                        bus->dhd->dongle_reset = FALSE;
                        bus->dhd->up = TRUE;

#if !defined(IGNORE_ETH0_DOWN)
                        /* Restore flow control  */
                        dhd_txflowcontrol(bus->dhd, ALL_INTERFACES, OFF);
#endif // endif
                        dhd_os_wd_timer(dhdp, dhd_watchdog_ms);

                        DHD_TRACE(("%s: WLAN ON DONE\n", __FUNCTION__));
                    } else {
                        dhd_bus_stop(bus, FALSE);
                        dhdsdio_release_dongle(bus, bus->dhd->osh, TRUE, FALSE);
                    }
                } else {
                    DHD_ERROR(("%s Failed to download binary to the dongle\n",
                               __FUNCTION__));
                    if (bus->sih != NULL) {
                        si_detach(bus->sih);
                        bus->sih = NULL;
                    }
                    bcmerror = BCME_SDIO_ERROR;
                }
            } else {
                bcmerror = BCME_SDIO_ERROR;
            }

            dhd_os_sdunlock(dhdp);
        } else {
            printf("%s called when dongle is not in reset\n", __FUNCTION__);
            printf("Will call dhd_bus_start instead\n");
            dhd_bus_resume(dhdp, 1);
#if defined(HW_OOB) || defined(FORCE_WOWLAN)
            dhd_conf_set_hw_oob_intr(
                bus->sdh,
                bus->sih); // terence 20120615: fix for OOB initial issue
#endif
            if ((bcmerror = dhd_bus_start(dhdp)) != 0) {
                DHD_ERROR(("%s: dhd_bus_start fail with %d\n", __FUNCTION__,
                           bcmerror));
            }
        }
    }

#ifdef PKT_STATICS
    dhd_bus_clear_txpktstatics(dhdp);
#endif
    return bcmerror;
}

int dhd_bus_suspend(dhd_pub_t *dhdpub)
{
    return bcmsdh_stop(dhdpub->bus->sdh);
}

int dhd_bus_resume(dhd_pub_t *dhdpub, int stage)
{
    return bcmsdh_start(dhdpub->bus->sdh, stage);
}

/* Get Chip ID version */
uint dhd_bus_chip_id(dhd_pub_t *dhdp)
{
    dhd_bus_t *bus = dhdp->bus;

    if (bus && bus->sih) {
        return bus->sih->chip;
    } else {
        return 0;
    }
}

/* Get Chip Rev ID version */
uint dhd_bus_chiprev_id(dhd_pub_t *dhdp)
{
    dhd_bus_t *bus = dhdp->bus;

    if (bus && bus->sih) {
        return bus->sih->chiprev;
    } else {
        return 0;
    }
}

/* Get Chip Pkg ID version */
uint dhd_bus_chippkg_id(dhd_pub_t *dhdp)
{
    dhd_bus_t *bus = dhdp->bus;

    return bus->sih->chippkg;
}

int dhd_bus_get_ids(struct dhd_bus *bus, uint32 *bus_type, uint32 *bus_num,
                    uint32 *slot_num)
{
    *bus_type = bus->bus;
    *bus_num = bus->bus_num;
    *slot_num = bus->slot_num;
    return 0;
}

int dhd_bus_membytes(dhd_pub_t *dhdp, bool set, uint32 address, uint8 *data,
                     uint size)
{
    dhd_bus_t *bus;

    bus = dhdp->bus;
    return dhdsdio_membytes(bus, set, address, data, size);
}

#if defined(SUPPORT_MULTIPLE_REVISION)
static int concate_revision_bcm4335(dhd_bus_t *bus, char *fw_path,
                                    char *nv_path)
{
    uint chipver;
#if defined(SUPPORT_MULTIPLE_CHIPS)
    char chipver_tag[0xA] = "_4335";
#else
    char chipver_tag[0x4] = {
        0,
    };
#endif /* defined(SUPPORT_MULTIPLE_CHIPS) */
    DHD_TRACE(("%s: BCM4335 Multiple Revision Check\n", __FUNCTION__));
    if (bus->sih->chip != BCM4335_CHIP_ID) {
        DHD_ERROR(("%s:Chip is not BCM4335\n", __FUNCTION__));
        return -1;
    }
    chipver = bus->sih->chiprev;
    DHD_ERROR(("CHIP VER = [0x%x]\n", chipver));
    if (chipver == 0x0) {
        DHD_ERROR(("----- CHIP bcm4335_A0 -----\n"));
        strcat(chipver_tag, "_a0");
    } else if (chipver == 0x1) {
        DHD_ERROR(("----- CHIP bcm4335_B0 -----\n"));
#if defined(SUPPORT_MULTIPLE_CHIPS)
        strcat(chipver_tag, "_b0");
#endif /* defined(SUPPORT_MULTIPLE_CHIPS) */
    }

    strcat(fw_path, chipver_tag);
    strcat(nv_path, chipver_tag);
    return 0;
}

static int concate_revision_bcm4339(dhd_bus_t *bus, char *fw_path,
                                    char *nv_path)
{
    uint chipver;
#if defined(SUPPORT_MULTIPLE_CHIPS)
    char chipver_tag[0xA] = "_4339";
#else
    char chipver_tag[0x4] = {
        0,
    };
#endif /* defined(SUPPORT_MULTIPLE_CHIPS) */

    DHD_TRACE(("%s: BCM4339 Multiple Revision Check\n", __FUNCTION__));
    if (bus->sih->chip != BCM4339_CHIP_ID) {
        DHD_ERROR(("%s:Chip is not BCM4339\n", __FUNCTION__));
        return -1;
    }
    chipver = bus->sih->chiprev;
    DHD_ERROR(("CHIP VER = [0x%x]\n", chipver));
    if (chipver == 0x1) {
        DHD_ERROR(("----- CHIP bcm4339_A0 -----\n"));
        strcat(chipver_tag, "_a0");
    } else {
        DHD_ERROR(("----- CHIP bcm4339 unknown revision %d -----\n", chipver));
    }

    strcat(fw_path, chipver_tag);
    strcat(nv_path, chipver_tag);
    return 0;
}

static int concate_revision_bcm4350(dhd_bus_t *bus, char *fw_path,
                                    char *nv_path)
{
    uint32 chip_ver;
#if defined(SUPPORT_MULTIPLE_CHIPS)
    char chipver_tag[0xA] = {
        0,
    };
#else
    char chipver_tag[0x4] = {
        0,
    };
#endif /* defined(SUPPORT_MULTIPLE_CHIPS) */
    chip_ver = bus->sih->chiprev;

#if defined(SUPPORT_MULTIPLE_CHIPS)
    if (chip_ver == 0x3) {
        strcat(chipver_tag, "_4354");
    } else {
        strcat(chipver_tag, "_4350");
    }
#endif // endif

    if (chip_ver == 0x3) {
        DHD_ERROR(("----- CHIP 4354 A0 -----\n"));
        strcat(chipver_tag, "_a0");
    } else {
        DHD_ERROR(("----- Unknown chip version, ver=%x -----\n", chip_ver));
    }

    strcat(fw_path, chipver_tag);
    strcat(nv_path, chipver_tag);
    return 0;
}

static int concate_revision_bcm4354(dhd_bus_t *bus, char *fw_path,
                                    char *nv_path)
{
    uint32 chip_ver;
#if defined(SUPPORT_MULTIPLE_CHIPS)
    char chipver_tag[0xA] = "_4354";
#else
    char chipver_tag[0x4] = {
        0,
    };
#endif /* SUPPORT_MULTIPLE_CHIPS */

    chip_ver = bus->sih->chiprev;
    if (chip_ver == 1) {
        DHD_ERROR(("----- CHIP 4354 A1 -----\n"));
        strcat(chipver_tag, "_a1");
    } else {
        DHD_ERROR(("----- Unknown chip version, ver=%x -----\n", chip_ver));
    }

    strcat(fw_path, chipver_tag);
    strcat(nv_path, chipver_tag);

    return 0;
}

static int concate_revision_bcm43454(dhd_bus_t *bus, char *fw_path,
                                     char *nv_path)
{
    char chipver_tag[0xA] = {
        0,
    };
#ifdef SUPPORT_MULTIPLE_BOARD_REV_FROM_DT
    int base_system_rev_for_nv = 0;
#endif /* SUPPORT_MULTIPLE_BOARD_REV_FROM_DT */

    DHD_TRACE(("%s: BCM43454 Multiple Revision Check\n", __FUNCTION__));
    if (bus->sih->chip != BCM43454_CHIP_ID) {
        DHD_ERROR(("%s:Chip is not BCM43454!\n", __FUNCTION__));
        return -1;
    }
#ifdef SUPPORT_MULTIPLE_BOARD_REV_FROM_DT
    base_system_rev_for_nv = dhd_get_system_rev();
    if (base_system_rev_for_nv > 0) {
        DHD_ERROR(("----- Board Rev  [%d] -----\n", base_system_rev_for_nv));
        sprintf(chipver_tag, "_r%02d", base_system_rev_for_nv);
    }
#endif /* SUPPORT_MULTIPLE_BOARD_REV_FROM_DT */
#ifdef SUPPORT_MULTIPLE_BOARD_REV_FROM_HW
    DHD_ERROR(("----- Rev [%d] Fot MULTIPLE Board. -----\n", system_hw_rev));
    if ((system_hw_rev >= 0x8) && (system_hw_rev <= 0xB)) {
        DHD_ERROR(("This HW is Rev 08 ~ 11. this is For FD-HW\n"));
        strcat(chipver_tag, "_FD");
    }
#endif /* SUPPORT_MULTIPLE_BOARD_REV_FROM_HW */

    strcat(nv_path, chipver_tag);
    return 0;
}

int concate_revision(dhd_bus_t *bus, char *fw_path, char *nv_path)
{
    int res = 0;

    if (!bus || !bus->sih) {
        DHD_ERROR(("%s:Bus is Invalid\n", __FUNCTION__));
        return -1;
    }

    switch (bus->sih->chip) {
        case BCM4335_CHIP_ID:
            res = concate_revision_bcm4335(bus, fw_path, nv_path);

            break;
        case BCM4339_CHIP_ID:
            res = concate_revision_bcm4339(bus, fw_path, nv_path);
            break;
        case BCM4350_CHIP_ID:
            res = concate_revision_bcm4350(bus, fw_path, nv_path);
            break;
        case BCM4354_CHIP_ID:
            res = concate_revision_bcm4354(bus, fw_path, nv_path);
            break;
        case BCM43454_CHIP_ID:
            res = concate_revision_bcm43454(bus, fw_path, nv_path);
            break;

        default:
            DHD_ERROR(("REVISION SPECIFIC feature is not required\n"));
            return res;
    }

    if (res == 0) {
    }
    return res;
}
#endif /* SUPPORT_MULTIPLE_REVISION */

void dhd_bus_update_fw_nv_path(struct dhd_bus *bus, char *pfw_path,
                               char *pnv_path, char *pclm_path,
                               char *pconf_path)
{
    bus->fw_path = pfw_path;
    bus->nv_path = pnv_path;
    bus->dhd->clm_path = pclm_path;
    bus->dhd->conf_path = pconf_path;
}

int dhd_enableOOB(dhd_pub_t *dhd, bool sleep)
{
    dhd_bus_t *bus = dhd->bus;
    sdpcmd_regs_t *regs = bus->regs;
    uint retries = 0;

    if (sleep) {
        dhdsdio_clkctl(bus, CLK_AVAIL, FALSE);
        /* Tell device to start using OOB wakeup */
        W_SDREG(SMB_USE_OOB, &regs->tosbmailbox, retries);
        if (retries > retry_limit) {
            DHD_ERROR(("CANNOT SIGNAL CHIP, WILL NOT WAKE UP!!\n"));
            return BCME_BUSY;
        }
        /* Turn off our contribution to the HT clock request */
        dhdsdio_clkctl(bus, CLK_SDONLY, FALSE);
    } else {
        /* Make sure the controller has the bus up */
        dhdsdio_clkctl(bus, CLK_AVAIL, FALSE);

        /* Send misc interrupt to indicate OOB not needed */
        W_SDREG(0, &regs->tosbmailboxdata, retries);
        if (retries <= retry_limit) {
            W_SDREG(SMB_DEV_INT, &regs->tosbmailbox, retries);
        }

        if (retries > retry_limit) {
            DHD_ERROR(("CANNOT SIGNAL CHIP TO CLEAR OOB!!\n"));
        }

        /* Make sure we have SD bus access */
        dhdsdio_clkctl(bus, CLK_SDONLY, FALSE);
    }
    return BCME_OK;
}

void dhd_bus_pktq_flush(dhd_pub_t *dhdp)
{
    dhd_bus_t *bus = dhdp->bus;
    bool wlfc_enabled = FALSE;

#ifdef PROP_TXSTATUS
    wlfc_enabled = (dhd_wlfc_cleanup_txq(dhdp, NULL, 0) != WLFC_UNSUPPORTED);
#endif // endif
    if (!wlfc_enabled) {
#ifdef DHDTCPACK_SUPPRESS
        /* Clean tcp_ack_info_tbl in order to prevent access to flushed pkt,
         * when there is a newly coming packet from network stack.
         */
        dhd_tcpack_info_tbl_clean(bus->dhd);
#endif /* DHDTCPACK_SUPPRESS */
        /* Clear the data packet queues */
        pktq_flush(dhdp->osh, &bus->txq, TRUE);
    }
}

#ifdef BCMSDIO
int dhd_sr_config(dhd_pub_t *dhd, bool on)
{
    dhd_bus_t *bus = dhd->bus;

    if (!bus->_srenab) {
        return -1;
    }

    return dhdsdio_clk_devsleep_iovar(bus, on);
}

uint16 dhd_get_chipid(dhd_pub_t *dhd)
{
    dhd_bus_t *bus = dhd->bus;

    if (bus && bus->sih) {
        return (uint16)bus->sih->chip;
    } else {
        return 0;
    }
}
#endif /* BCMSDIO */

#ifdef DEBUGGER
static uint32 dhd_sdio_reg_read(struct dhd_bus *bus, ulong addr)
{
    uint32 rval;

    dhd_os_sdlock(bus->dhd);

    BUS_WAKE(bus);

    dhdsdio_clkctl(bus, CLK_AVAIL, FALSE);

    rval = bcmsdh_reg_read(bus->sdh, addr, 0x4);

    dhd_os_sdunlock(bus->dhd);

    return rval;
}

static void dhd_sdio_reg_write(struct dhd_bus *bus, ulong addr, uint32 val)
{
    dhd_os_sdlock(bus->dhd);

    BUS_WAKE(bus);

    dhdsdio_clkctl(bus, CLK_AVAIL, FALSE);

    bcmsdh_reg_write(bus->sdh, addr, 0x4, val);

    dhd_os_sdunlock(bus->dhd);
}

#endif /* DEBUGGER */

#if defined(BT_OVER_SDIO)
uint8 dhd_bus_cfg_read(void *h, uint fun_num, uint32 addr, int *err)
{
    uint8 intrd;
    dhd_pub_t *dhdp = (dhd_pub_t *)h;
    dhd_bus_t *bus = (dhd_bus_t *)dhdp->bus;

    dhd_os_sdlock(bus->dhd);

    intrd = bcmsdh_cfg_read(bus->sdh, fun_num, addr, err);

    dhd_os_sdunlock(bus->dhd);

    return intrd;
}
EXPORT_SYMBOL(dhd_bus_cfg_read);

void dhd_bus_cfg_write(void *h, uint fun_num, uint32 addr, uint8 val, int *err)
{
    dhd_pub_t *dhdp = (dhd_pub_t *)h;
    dhd_bus_t *bus = (dhd_bus_t *)dhdp->bus;

    dhd_os_sdlock(bus->dhd);

    bcmsdh_cfg_write(bus->sdh, fun_num, addr, val, err);

    dhd_os_sdunlock(bus->dhd);
}
EXPORT_SYMBOL(dhd_bus_cfg_write);

static int extract_hex_field(char *line, uint16 start_pos, uint16 num_chars,
                             uint16 *value)
{
    char field[0x8];

    strncpy(field, line + start_pos, num_chars);
    field[num_chars] = '\0';

    return (sscanf(field, "%hX", value) == 1);
}

static int read_more_btbytes(struct dhd_bus *bus, void *file, char *line,
                             int *addr_mode, uint16 *hi_addr, uint32 *dest_addr,
                             uint8 *data_bytes, uint32 *num_bytes)
{
    int str_len;
    uint16 num_data_bytes, addr, data_pos, type, w, i;
    uint32 abs_base_addr32 = 0;
    *num_bytes = 0;

    while (!*num_bytes) {
        str_len = dhd_os_gets_image(bus->dhd, line, BTFW_MAX_STR_LEN, file);

        DHD_TRACE(("%s: Len :0x%x  %s\n", __FUNCTION__, str_len, line));

        if (str_len == 0) {
            break;
        } else if (str_len > 0x9) {
            extract_hex_field(line, 1, 0x2, &num_data_bytes);
            extract_hex_field(line, 0x3, 0x4, &addr);
            extract_hex_field(line, 0x7, 0x2, &type);

            data_pos = 0x9;
            for (i = 0; i < num_data_bytes; i++) {
                extract_hex_field(line, data_pos, 0x2, &w);
                data_bytes[i] = (uint8)(w & 0x00FF);
                data_pos += 0x2;
            }

            if (type == BTFW_HEX_LINE_TYPE_EXTENDED_ADDRESS) {
                *hi_addr = (data_bytes[0] << 0x8) | data_bytes[1];
                *addr_mode = BTFW_ADDR_MODE_EXTENDED;
            } else if (type == BTFW_HEX_LINE_TYPE_EXTENDED_SEGMENT_ADDRESS) {
                *hi_addr = (data_bytes[0] << 0x8) | data_bytes[1];
                *addr_mode = BTFW_ADDR_MODE_SEGMENT;
            } else if (type == BTFW_HEX_LINE_TYPE_ABSOLUTE_32BIT_ADDRESS) {
                abs_base_addr32 = (data_bytes[0] << 0x18) |
                                  (data_bytes[1] << 0x10) | (data_bytes[0x2] << 0x8) |
                                  data_bytes[0x3];
                *addr_mode = BTFW_ADDR_MODE_LINEAR32;
            } else if (type == BTFW_HEX_LINE_TYPE_DATA) {
                *dest_addr = addr;
                if (*addr_mode == BTFW_ADDR_MODE_EXTENDED) {
                    *dest_addr += (*hi_addr << 0x10);
                } else if (*addr_mode == BTFW_ADDR_MODE_SEGMENT) {
                    *dest_addr += (*hi_addr << 0x4);
                } else if (*addr_mode == BTFW_ADDR_MODE_LINEAR32) {
                    *dest_addr += abs_base_addr32;
                }
                *num_bytes = num_data_bytes;
            }
        }
    }
    return (*num_bytes > 0);
}

static int _dhdsdio_download_btfw(struct dhd_bus *bus)
{
    int bcm_error = -1;
    void *image = NULL;
    uint8 *mem_blk = NULL, *mem_ptr = NULL, *data_ptr = NULL;

    uint32 offset_addr = 0, offset_len = 0, bytes_to_write = 0;

    char *line = NULL;
    uint32 dest_addr = 0, num_bytes;
    uint16 hiAddress = 0;
    uint32 start_addr, start_data, end_addr, end_data, i, index, pad,
        bt2wlan_pwrup_adr;

    int addr_mode = BTFW_ADDR_MODE_EXTENDED;

    /* Out immediately if no image to download */
    if ((bus->btfw_path == NULL) || (bus->btfw_path[0] == '\0')) {
        return 0;
    }

    image = dhd_os_open_image1(bus->dhd, bus->btfw_path);
    if (image == NULL) {
        goto err;
    }

    mem_ptr = mem_blk =
        MALLOC(bus->dhd->osh, BTFW_DOWNLOAD_BLK_SIZE + DHD_SDALIGN);
    if (mem_blk == NULL) {
        DHD_ERROR(("%s: Failed to allocate memory %d bytes\n", __FUNCTION__,
                   BTFW_DOWNLOAD_BLK_SIZE + DHD_SDALIGN));
        goto err;
    }
    if ((uint32)(uintptr)mem_blk % DHD_SDALIGN) {
        mem_ptr += (DHD_SDALIGN - ((uint32)(uintptr)mem_blk % DHD_SDALIGN));
    }

    data_ptr = MALLOC(bus->dhd->osh, BTFW_DOWNLOAD_BLK_SIZE - 0x8);
    if (data_ptr == NULL) {
        DHD_ERROR(("%s: Failed to allocate memory %d bytes\n", __FUNCTION__,
                   BTFW_DOWNLOAD_BLK_SIZE - 0x8));
        goto err;
    }
    /* Write to BT register to hold WLAN wake high during BT FW download */
    bt2wlan_pwrup_adr = BTMEM_OFFSET + BT2WLAN_PWRUP_ADDR;
    bcmsdh_reg_write(bus->sdh, bt2wlan_pwrup_adr, 0x4, BT2WLAN_PWRUP_WAKE);
    /*
     * Wait for at least 2msec for the clock to be ready/Available.
     */
    OSL_DELAY(0x7D0);

    line = MALLOC(bus->dhd->osh, BTFW_MAX_STR_LEN);
    if (line == NULL) {
        DHD_ERROR(("%s: Failed to allocate memory %d bytes\n", __FUNCTION__,
                   BTFW_MAX_STR_LEN));
        goto err;
    }
    memset(line, 0, BTFW_MAX_STR_LEN);

    while (read_more_btbytes(bus, image, line, &addr_mode, &hiAddress,
                             &dest_addr, data_ptr, &num_bytes)) {
        DHD_TRACE(("read %d bytes at address %08X\n", num_bytes, dest_addr));

        start_addr = BTMEM_OFFSET + dest_addr;
        index = 0;

        /* Make sure the start address is 4 byte aligned to avoid alignment
         * issues with SD host controllers
         */
        if (!ISALIGNED(start_addr, 0x4)) {
            pad = start_addr % 0x4;
            start_addr = ROUNDDN(start_addr, 0x4);
            start_data = bcmsdh_reg_read(bus->sdh, start_addr, 0x4);
            for (i = 0; i < pad; i++, index++) {
                mem_ptr[index] = (uint8)((uint8 *)&start_data)[i];
            }
        }
        bcopy(data_ptr, &(mem_ptr[index]), num_bytes);
        index += num_bytes;

        /* Make sure the length is multiple of 4bytes to avoid alignment issues
         * with SD host controllers
         */
        end_addr = start_addr + index;
        if (!ISALIGNED(end_addr, 0x4)) {
            end_addr = ROUNDDN(end_addr, 0x4);
            end_data = bcmsdh_reg_read(bus->sdh, end_addr, 0x4);
            for (i = (index % 0x4); i < 0x4; i++, index++) {
                mem_ptr[index] = (uint8)((uint8 *)&end_data)[i];
            }
        }

        offset_addr = start_addr & 0xFFF;
        offset_len = offset_addr + index;
        if (offset_len <= 0x1000) {
            bcm_error = dhdsdio_membytes(bus, TRUE, start_addr, mem_ptr, index);
            if (bcm_error) {
                DHD_ERROR(("%s: error %d on writing %d membytes at 0x%08x\n",
                           __FUNCTION__, bcm_error, num_bytes, start_addr));
                goto err;
            }
        } else {
            bytes_to_write = 0x1000 - offset_addr;
            bcm_error = dhdsdio_membytes(bus, TRUE, start_addr, mem_ptr,
                                         bytes_to_write);
            if (bcm_error) {
                DHD_ERROR(("%s: error %d on writing %d membytes at 0x%08x\n",
                           __FUNCTION__, bcm_error, num_bytes, start_addr));
                goto err;
            }

            OSL_DELAY(0x2710);

            bcm_error = dhdsdio_membytes(
                bus, TRUE, (start_addr + bytes_to_write),
                (mem_ptr + bytes_to_write), (index - bytes_to_write));
            if (bcm_error) {
                DHD_ERROR(("%s: error %d on writing %d membytes at 0x%08x\n",
                           __FUNCTION__, bcm_error, num_bytes, start_addr));
                goto err;
            }
        }
        memset(line, 0, BTFW_MAX_STR_LEN);
    }

    bcm_error = 0;
err:
    if (mem_blk) {
        MFREE(bus->dhd->osh, mem_blk, BTFW_DOWNLOAD_BLK_SIZE + DHD_SDALIGN);
    }

    if (data_ptr) {
        MFREE(bus->dhd->osh, data_ptr, BTFW_DOWNLOAD_BLK_SIZE - 0x8);
    }

    if (line) {
        MFREE(bus->dhd->osh, line, BTFW_MAX_STR_LEN);
    }

    if (image) {
        dhd_os_close_image1(bus->dhd, image);
    }

    return bcm_error;
}

static int dhdsdio_download_btfw(struct dhd_bus *bus, osl_t *osh, void *sdh)
{
    int ret;

    DHD_TRACE(("%s: btfw path=%s\n", __FUNCTION__, bus->btfw_path));
    DHD_OS_WAKE_LOCK(bus->dhd);
    dhd_os_sdlock(bus->dhd);

    /* Download the firmware */
    ret = _dhdsdio_download_btfw(bus);

    dhd_os_sdunlock(bus->dhd);
    DHD_OS_WAKE_UNLOCK(bus->dhd);

    return ret;
}

int dhd_bus_download_btfw(struct dhd_bus *bus, osl_t *osh, char *pbtfw_path)
{
    int ret;

    bus->btfw_path = pbtfw_path;

    ret = dhdsdio_download_btfw(bus, osh, bus->sdh);

    return ret;
}
#endif /* defined (BT_OVER_SDIO) */

void dhd_bus_dump_trap_info(dhd_bus_t *bus, struct bcmstrbuf *strbuf)
{
    trap_t *tr = &bus->dhd->last_trap_info;

    bcm_bprintf(
        strbuf,
        "Dongle trap type 0x%x @ epc 0x%x, cpsr 0x%x, spsr 0x%x, sp 0x%x,"
        "lp 0x%x, rpc 0x%x Trap offset 0x%x, "
        "r0 0x%x, r1 0x%x, r2 0x%x, r3 0x%x, "
        "r4 0x%x, r5 0x%x, r6 0x%x, r7 0x%x\n\n",
        ltoh32(tr->type), ltoh32(tr->epc), ltoh32(tr->cpsr), ltoh32(tr->spsr),
        ltoh32(tr->r13), ltoh32(tr->r14), ltoh32(tr->pc),
        ltoh32(bus->dongle_trap_addr), ltoh32(tr->r0), ltoh32(tr->r1),
        ltoh32(tr->r2), ltoh32(tr->r3), ltoh32(tr->r4), ltoh32(tr->r5),
        ltoh32(tr->r6), ltoh32(tr->r7));
}

static int dhd_bcmsdh_send_buffer(void *bus, uint8 *frame, uint16 len)
{
    int ret = -1;

    ret = dhd_bcmsdh_send_buf(bus, bcmsdh_cur_sbwad(((dhd_bus_t *)bus)->sdh),
                              SDIO_FUNC_2, F2SYNC, frame, len, NULL, NULL, NULL,
                              TXRETRIES);
    if (ret == BCME_OK) {
        ((dhd_bus_t *)bus)->tx_seq =
            (((dhd_bus_t *)bus)->tx_seq + 1) % SDPCM_SEQUENCE_WRAP;
    }
    return ret;
}

/* Function to set the min res mask depending on the chip ID used */
bool dhd_bus_set_default_min_res_mask(struct dhd_bus *bus)
{
    if ((bus == NULL) || (bus->sih == NULL)) {
        DHD_ERROR(("%s(): Invalid Arguments \r\n", __FUNCTION__));
        return FALSE;
    }

    switch (bus->sih->chip) {
        case BCM4339_CHIP_ID:
            bcmsdh_reg_write(bus->sdh, SI_ENUM_BASE(bus->sih) + 0x618, 0x4,
                             0x3fcaf377);
            if (bcmsdh_regfail(bus->sdh)) {
                DHD_ERROR(("%s:%d Setting min_res_mask failed\n", __FUNCTION__,
                           __LINE__));
                return FALSE;
            }
            break;

        case BCM43012_CHIP_ID:
            bcmsdh_reg_write(bus->sdh,
                             si_get_pmu_reg_addr(
                                 bus->sih, OFFSETOF(pmuregs_t, min_res_mask)),
                             0x4, DEFAULT_43012_MIN_RES_MASK);
            if (bcmsdh_regfail(bus->sdh)) {
                DHD_ERROR(("%s:%d Setting min_res_mask failed\n", __FUNCTION__,
                           __LINE__));
                return FALSE;
            }
            break;

        default:
            DHD_ERROR(("%s: Unhandled chip id\n", __FUNCTION__));
            return FALSE;
    }

    return TRUE;
}

/* Function to reset PMU registers */
void dhd_bus_pmu_reg_reset(dhd_pub_t *dhdp)
{
    struct dhd_bus *bus = dhdp->bus;
    bcmsdh_reg_write(
        bus->sdh, si_get_pmu_reg_addr(bus->sih, OFFSETOF(pmuregs_t, swscratch)),
        0x4, 0x0);
    if (bcmsdh_regfail(bus->sdh)) {
        DHD_ERROR(
            ("%s:%d Setting min_res_mask failed\n", __FUNCTION__, __LINE__));
    }
}

#ifdef DHD_ULP

/* Function to disable console messages on entering ULP mode */
void dhd_bus_ulp_disable_console(dhd_pub_t *dhdp)
{
#ifdef DHD_DEBUG
    DHD_ERROR(("Flushing and disabling console messages\n"));

    /* Save the console print interval */
    dhd_ulp_save_console_interval(dhdp);

    /* Flush the console buffer before disabling */
    dhdsdio_readconsole(dhdp->bus);
    dhdp->dhd_console_ms = 0;
#endif /* DHD_DEBUG */
}

/* Function for redownloading firmaware */
static int dhd_bus_ulp_reinit_fw(dhd_bus_t *bus)
{
    int bcmerror = 0;

    /* After firmware redownload tx/rx seq are reset accordingly these values
    are reset on DHD side tx_max is initially set to 4, which later is updated
    by FW
    */
    bus->tx_seq = bus->rx_seq = 0;
    bus->tx_max = 0x4;

    if (dhd_bus_download_firmware(bus, bus->dhd->osh, bus->fw_path,
                                  bus->nv_path) >= 0) {
        /* Re-init bus, enable F2 transfer */
        bcmerror = dhd_bus_init((dhd_pub_t *)bus->dhd, FALSE);
        if (bcmerror == BCME_OK) {
            bus->dhd->up = TRUE;
            dhd_os_wd_timer(bus->dhd, dhd_watchdog_ms);

            dhd_ulp_set_ulp_state(bus->dhd, DHD_ULP_READY);
#if defined(OOB_INTR_ONLY)
            dhd_enable_oob_intr(bus, TRUE);
            bcmsdh_oob_intr_set(bus->sdh, TRUE);
#endif /* defined(OOB_INTR_ONLY) */
#ifdef DHD_DEBUG
            /* Re-enable the console messages on FW redownload to default value
             */
            dhd_ulp_restore_console_interval(bus->dhd);
#endif /* DHD_DEBUG */
        } else {
            DHD_ERROR(("bus init failed\n"));
            dhd_bus_stop(bus, FALSE);
            dhdsdio_release_dongle(bus, bus->dhd->osh, TRUE, FALSE);
        }
    } else {
        bcmerror = BCME_SDIO_ERROR;
    }

    return bcmerror;
}
#endif /* DHD_ULP */

int dhd_bus_readwrite_bp_addr(dhd_pub_t *dhdp, uint addr, uint size, uint *data,
                              bool read)
{
    int bcmerror = 0;
    struct dhd_bus *bus = dhdp->bus;

    if (read) {
        *data = (int32)bcmsdh_reg_read(bus->sdh, addr, size);
    } else {
        bcmsdh_reg_write(bus->sdh, addr, size, *data);
    }

    if (bcmsdh_regfail(bus->sdh)) {
        bcmerror = BCME_SDIO_ERROR;
    }

    return bcmerror;
}

int dhd_get_idletime(dhd_pub_t *dhd)
{
    return dhd->bus->idletime;
}

#ifdef DHD_WAKE_STATUS
wake_counts_t *dhd_bus_get_wakecount(dhd_pub_t *dhd)
{
    if (!dhd->bus) {
        return NULL;
    }
    return &dhd->bus->wake_counts;
}
int dhd_bus_get_bus_wake(dhd_pub_t *dhd)
{
    return bcmsdh_set_get_wake(dhd->bus->sdh, 0);
}
#endif /* DHD_WAKE_STATUS */

int dhd_bus_sleep(dhd_pub_t *dhdp, bool sleep, uint32 *intstatus)
{
    dhd_bus_t *bus = dhdp->bus;
    uint32 retry = 0;
    int ret = 0;

    if (bus) {
        dhd_os_sdlock(dhdp);
        BUS_WAKE(bus);
        R_SDREG(*intstatus, &bus->regs->intstatus, retry);
        if (sleep) {
            if (SLPAUTO_ENAB(bus)) {
                ret = dhdsdio_bussleep(bus, sleep);
                if (ret != BCME_BUSY) {
                    dhd_os_wd_timer(bus->dhd, 0);
                }
            } else {
                dhdsdio_clkctl(bus, CLK_NONE, FALSE);
            }
        }
        dhd_os_sdunlock(dhdp);
    } else {
        DHD_ERROR(("bus is NULL\n"));
        ret = -1;
    }

    return ret;
}
